我正在尝试在 linux 上调试一个问题,有时我的 TCP 服务器会永远阻塞在套接字上,尽管套接字已经消失了。我编写了测试代码来理解 close()、shutdown()、select() 和 recv() 的行为。
我有 2 个线程 - 线程 1 使用 select() 在套接字上阻塞,线程 2 在同一个套接字上调用 shutdown(SHUT_RDWR)。当从线程 2 调用 shutdown() 时,线程 1 唤醒,select() 返回 1,但 errno 读取 0。此外,在 select() 返回 1 后,调用 recv() 并返回 0,errno 再次读取 0。
如果我将线程 2 实现更改为使用 close() 而不是 shutdown(),则 select() 永远不会唤醒。
如果我将线程 2 实现更改为调用 shutdown(),然后调用 close(),则 select() 返回 1(errno 保持为 0),然后 recv() 返回 -1 并且 errno 读取 EBADF。
我的问题:
调用 close() 时, select() 不应该唤醒吗?我查看了 close() 手册页,但无法确定应该发生什么。
调用 shutdown(SHUT_RDWR) 时, select() 是否应该像我看到的那样正常返回?还是应该返回 1 并将 errno 设置为 EBADF?如果我查看 select() 手册页,它会说如果一个或多个文件描述符集指定的文件描述符不是有效的打开文件描述符,则将 errno 设置为 EBADF。所以看起来shutdown()只向远程端发送TCP FIN,但实际上并没有关闭套接字fd?
如果我调用shutdown() 然后调用close(),它似乎有帮助。这与单独调用 close() 有何不同?
注意:我知道在一个线程中阻塞套接字并从另一个线程关闭同一个套接字不是一个好的设计,但我正在处理遗留代码库,所以我不能改变这个设计。我只能将其调整为调用 shutdown() 然后 close() 因为它似乎有效,但我想确保我得到正确的基本理解。
提前致谢。
这是测试代码:
线程 1:
#include "sys/socket.h"
#include "net/if.h"
#include "linux/sockios.h"
#include "netinet/in.h"
#include "fcntl.h"
#include "signal.h"
#include "errno.h"
#include "strings.h"
#include "pthread.h"
#include "errno.h"
void* f1(void* p)
{
int f1_ret, bytes, ret;
struct sockaddr_in f1so1_addr;
fd_set read_f1, error_fds;
char f1buf[20];
struct sockaddr clientInfo;
socklen_t clientAddrLen = sizeof(struct sockaddr);
f1so1 = socket(AF_INET, SOCK_STREAM, 0);
printf("f1: open fd's - f1fd1 = %d, f1so1 = %d\n", f1fd1, f1so1);
f1so1_addr.sin_family = AF_INET;
f1so1_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//ret = inet_aton("10.20.30.100", &(f1so1_addr.sin_addr));
f1so1_addr.sin_port = htons(7777);
ret = bind(f1so1, (const struct sockaddr*) &f1so1_addr, sizeof(f1so1_addr));
printf("f1: bind returned %x, errno = %x\n", ret, errno);
listen(f1so1, 5);
printf("f1- listening on f1so1, blocking on accept\n");
f1so2 = accept(f1so1, &clientInfo, &clientAddrLen);
printf("f1- accept returned %x, errno = %x\n", f1so2, errno);
if(errno)
{
printf("f1: accept failed, return...\n");
return;
}
FD_ZERO(&read_f1);
FD_SET(f1so2, &read_f1);
FD_ZERO(&error_fds);
FD_SET(f1so2, &error_fds);
printf("f1: start loop\n");
while (1)
{
printf("f1: call select _read and error__ - cBLOCKING\n");
errno = 0;
FD_ZERO(&read_f1);
FD_SET(f1so2, &read_f1);
FD_ZERO(&error_fds);
FD_SET(f1so2, &error_fds);
f1_ret = select(f1so2+1, &read_f1, 0, &error_fds, 0);
printf("f1: select returned = %x, errno = %x\n", f1_ret, errno);
if (errno)
{
printf("f1: select failed...\n");
}
else if (f1_ret)
{
if (FD_ISSET(f1so2,&error_fds))
{
printf("f1: error on socket %x\n",f1so2);
}
if (FD_ISSET(f1so2,&read_f1))
{
sleep(1);
bytes = recv(f1so2, f1buf, 20, 0);
printf("f1: errno after recv = %x\n", errno);
if (errno)
{
printf("!!!recv failed!!!\n");
return;
}
printf("f1: read from socket %x, bytes = %x, data = %s\n",f1so2, bytes, f1buf);
}
}
}
printf("f1: exiting\n");
}
线程 2:
#include "stdio.h"
#include "pthread.h"
#include "linux/socket.h"
#include "sys/socket.h"
#include "net/if.h"
#include "linux/sockios.h"
#include "netinet/in.h"
#include "fcntl.h"
#include "signal.h"
#include "errno.h"
#include "strings.h"
extern int f1so2;
void f2()
{
int f2_ret, i, choice;
struct sockaddr_in f2so2_addr;
fd_set read_f2;
char f2buf[20];
struct sigaction f2_act;
while (1)
{
printf("f2: 1: close socket, 2: shutdown socket, 2: exit f2\n");
scanf("%d", &choice);
if (choice == 1)
{
f2_ret = close(f1so2);
printf("f2: close on socket %x returned %x, errno = %x\n", f1so2, f2_ret, errno);
}
else if (choice == 2)
{
f2_ret = shutdown(f1so2, SHUT_RDWR);
printf("f2: shutdown on socket %x returned %x, errno = %x\n", f1so2, f2_ret, errno);
}
else if (choice == 3)
{
continue;
}
}
printf("f2: exiting\n");
return;
}