当套接字关闭时,解除recvfrom的阻塞状态

9
假设我启动了一个线程来接收某个端口的数据。socket调用将在recvfrom上阻塞。然后,在另一个线程中,我关闭了这个socket。
在Windows上,这将解除recvfrom的阻塞,我的线程执行将终止。
但在Linux上,这并不会解除recvfrom的阻塞,因此,我的线程将永远无所作为,并且线程执行不会终止。
有人能帮我解释一下在Linux上发生了什么吗?当socket被关闭时,我希望recvfrom能够解除阻塞。
我一直在阅读关于使用select()的文章,但不知道如何针对我的特定情况使用它。
4个回答

19

在套接字上调用shutdown(sock, SHUT_RDWR),然后等待线程退出。(即pthread_join)。

你会认为close()会解除对recvfrom()的阻塞,但在Linux上它并不会。


SHUT_RD就足够了。 - user207421
2
它不可能保证在任何系统上解除recvfrom的阻塞,因为存在固有的竞争问题,只能通过用户空间中的同步来解决。 - David Schwartz
1
在Linux中,shutdown(sock, SHUT_RDWR)完美地工作了...谢谢。 - M.Hefny

6
这里是一个简单的使用select()来处理此问题的草图:
// Note: untested code, may contain typos or bugs
static volatile bool _threadGoAway = false;

void MyThread(void *)
{
   int fd = (your socket fd);
   while(1)
   {
      struct timeval timeout = {1, 0};  // make select() return once per second

      fd_set readSet;
      FD_ZERO(&readSet);
      FD_SET(fd, &readSet);

      if (select(fd+1, &readSet, NULL, NULL, &timeout) >= 0)
      {
         if (_threadGoAway)
         {
            printf("MyThread:  main thread wants me to scram, bye bye!\n");
            return;
         }
         else if (FD_ISSET(fd, &readSet))
         {
            char buf[1024];
            int numBytes = recvfrom(fd, buf, sizeof(buf), 0);
            [...handle the received bytes here...]
         }
      }
      else perror("select");
   }
}

// To be called by the main thread at shutdown time
void MakeTheReadThreadGoAway()
{
   _threadGoAway = true;
   (void) pthread_join(_thread, NULL);   // may block for up to one second
}

一种更优雅的方法是避免使用select的超时功能,而是创建一个socket pair(使用socketpair()),当主线程想让I/O线程离开时,在socket pair的自己一端发送一个字节,并在另一端收到该套接字上的字节时退出I/O线程。但我会将其留给读者作为练习。 :)
此外,通常还应将套接字设置为非阻塞模式,以避免recvfrom()调用即使在select()指示套接字已准备好读取的情况下仍可能被阻塞,如这里所述。但是阻塞模式可能对您的目的“足够好”。

我再次提醒您,那个问题的“被接受”的答案是错的。从最初的BSD和SYS V开始,每个Unix都保证在select()表示套接字已准备好之后,read()永远不会阻塞。POSIX规范也是如此。如果Linux的行为不同,那么这是Linux的一个错误。你不应该鼓励人们使用垃圾代码来迎合损坏的系统。 - Nemo
嗨Nemo,如果你说Linux中的错误现在已经修复了,因此我的警告是误导性的,那就是一回事。但是,如果你说这个错误仍然存在,但是没有人可以谈论它,那么你只是让人们注定要失败。让人们对Linux的(不良)行为一无所知并不能防止问题困扰他们,只会阻止他们提前计划如何处理这个问题。 - Jeremy Friesner
一个很好的观点。我认为这个 bug 已经被修复的可能性不为零,因为毫无理由地如此明显地违反 POSIX 是愚蠢的,而 Linux 开发人员并不愚蠢。我将尝试在 linux-kernel 邮件列表上询问。如果我的语气过于敌对,我向您道歉;我今天过得不太好... - Nemo
@DavidSchwartz:显然,如果在selectread之间对套接字进行了某些操作(例如启用校验和强制执行,或者只是另一个对read的调用),则可能会更改其“准备好读取”的属性。我们正在谈论的是select表示套接字已准备好读取,然后立即read阻塞。没有任何Unix允许这样做,但Linux确实违反了POSIX(请参见http://pubs.opengroup.org/onlinepubs/9699919799/functions/select.html并搜索“considered ready”)。 - Nemo
@Nemo,这与“获取可用空间”函数没有什么不同,它表示可用空间意味着写入操作不会返回“无空间”错误。这并不意味着实际的未来写入操作不会返回“无空间”错误,因为实现程序无法完全控制磁盘空间并为您冻结世界。连接也是如此。 - David Schwartz
显示剩余3条评论

4

这不是一个答案,但Linux close手册包含了有趣的引用:

关闭文件描述符时,如果它们可能被同一进程中其他线程的系统调用使用,则可能是不明智的。由于文件描述符可能会被重用,因此存在一些模糊的竞态条件,可能会导致意外的副作用。


这就是为什么我说“在另一个线程中以某种方式关闭套接字......这种情况需要处理,我不想因为一个线程在关闭时被阻塞在等待recvfrom的套接字上而导致程序永远挂起的可能性。 - user657178
@user657178 不需要处理此情况,因为其他正确的代码不可能触发此情况。 - David Schwartz

4
你在要求不可能的事情。调用close的线程根本无法知道另一个线程是否在recvfrom中被阻塞。试着编写能够保证这种情况发生的代码,你会发现这是不可能的。
无论你做什么,调用close总是可能与调用recvfrom竞争。调用close会改变套接字描述符所指向的内容,因此它可以改变对recvfrom的语义理解。
线程进入recvfrom后,没有任何方法可以向调用close的线程发出信号,告诉它自己已经被阻塞(而不是即将被阻塞或刚刚进入系统调用)。因此,确保closerecvfrom的行为可预测是不可能的。
考虑以下情况:
1.一个线程即将调用recvfrom,但被系统需要执行的其他任务抢占了CPU资源。
2.稍后,该线程调用close
3.由I/O库启动的线程调用socket,并获得与你close的相同描述符。
4.最后,线程调用recvfrom,现在它正在从库打开的套接字中接收数据。
糟糕了。
永远不要做任何类似于这样的事情。当另一个线程正在使用或可能使用资源时,不得释放该资源。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接