套接字关闭时取消阻止 recvfrom

Posted

技术标签:

【中文标题】套接字关闭时取消阻止 recvfrom【英文标题】:Unblock recvfrom when socket is closed 【发布时间】:2011-06-17 18:12:53 【问题描述】:

假设我启动了一个线程以在端口上接收。套接字调用将在 recvfrom 上阻塞。 然后,不知何故在另一个线程中,我关闭了套接字。

在 Windows 上,这将解除对 recvfrom 的阻止,并且我的线程执行将终止。

在 Linux 上,这不会解除对 recvfrom 的阻塞,因此,我的线程永远什么都不做,线程执行也不会终止。

谁能帮助我了解 Linux 上正在发生的事情?当套接字关闭时,我希望 recvfrom 解除阻塞

我一直在阅读有关使用 select() 的信息,但我不知道如何在我的特定情况下使用它。

【问题讨论】:

【参考方案1】:

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

您可能认为close() 会解除对recvfrom() 的阻止,但它不会在 linux 上。

【讨论】:

SHUT_RD 就足够了。 不能,也不可能保证在 any 系统上解除对recvfrom 的阻止。存在一种固有的竞争,只能通过用户空间中的同步来解决。 shutdown(sock, SHUT_RDWR) 在 linux 中完美运行......谢谢【参考方案2】:

这是一个使用 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 的超时功能,而是创建一个套接字对(使用 socketpair()),并让主线程在它想要 I 时在其套接字对的末尾发送一个字节/O 线程离开,当 I/O 线程在套接字对的另一端的套接字上接收到一个字节时退出。不过,我将把它作为练习留给读者。 :)

将套接字设置为非阻塞模式通常也是一个好主意,以避免即使在 select() 指示套接字准备好之后,recvfrom() 调用也可能阻塞的(小但非零)机会 -阅读,如here 所述。但是对于您的目的,阻止模式可能“足够好”。

【讨论】:

我再次提醒您,该问题的“已接受”答案是完全错误的。每个 Unix——从最初的 BSD 和 SYS V 开始——都保证 read() 在 select() 表示套接字准备好之后将永远阻塞。 POSIX 规范也是如此。如果 Linux 的行为不同,那就是 Linux 中的一个错误。你不应该鼓励人们用垃圾污染他们的代码来迎合损坏的系统。 嗨 Nemo,如果您说 Linux 中的错误现在已修复,因此我的警告具有误导性,那是一回事。如果 OTOH 您说该错误仍然存​​在,但没有人必须谈论它,那么您只是在让人们失败。让人们不知道 Linux 的(错误)行为不会阻止问题对他们的影响,只会阻止他们提前计划如何处理问题。 公平点。我认为修复这个 bug 的可能性不为零,因为无缘无故公然违反 POSIX 是愚蠢的,而 Linux 开发人员也不愚蠢。我会尝试在 linux-kernel 邮件列表中询问。如果我的语气过于敌对,我深表歉意;我度过了糟糕的一天...... @Nemo 我在哪里可以找到这个保证?您认为它违反了 POSIX 的哪个条款?这对我来说听起来很疯狂。这意味着,例如,允许您“取消”未读数据的网络协议不能被 POSIX 支持,这听起来像是一个非凡的主张。我的理解是select 应该是协议中立的,如果不考虑协议规则,您无法保证read 不会阻塞。例如,为什么 UDP 实现在触发来自selectread 命中后不能丢弃数据报?违反了什么 POSIX 规则? @Nemo 这个场景在哪里违反了 POSIX? 1. UDP 校验和被禁用。 2. 在连接的套接字上接收到具有无效校验和的数据报。 3. select 调用解除阻塞,因为 read 不会阻塞。 4. 启用校验和强制。 5. read 调用现在阻塞,因为没有收到具有有效校验和的数据报。【参考方案3】:

不是答案,但 Linux 关闭手册页包含有趣的引用:

关闭文件描述符可能是不明智的,因为它们可能在 由同一进程中的其他线程中的系统调用使用。由于一个文件 描述符可能被重用,有一些模糊的竞争条件 可能会导致意想不到的副作用。

【讨论】:

这就是为什么我说“不知何故在另一个线程中,我关闭了套接字......这种情况需要处理,我不希望我的程序因为线程阻塞而永远挂起关闭时等待 recvfrom 的套接字 @user657178 这种情况不需要处理,因为否则正确的代码不能可能触发这种情况。【参考方案4】:

你要求的是不可能的。调用close 的线程根本不可能知道另一个线程在recvfrom 中被阻塞。尝试编写保证这种情况发生的代码,你会发现这是不可能的。

无论您做什么,对close 的调用总是有可能与对recvfrom 的调用竞争。对close的调用改变了套接字描述符所指的内容,因此它可以改变对recvfrom的调用的语义。

进入recvfrom 的线程无法以某种方式向调用close 的线程发出信号,表明它已被阻塞(而不是即将阻塞或刚刚进入系统调用)。所以实际上没有办法确保closerecvfrom 的行为是可预测的。

考虑以下几点:

    一个线程即将调用recvfrom,但它被系统需要做的其他事情抢占了。 稍后,线程调用close。 由系统 I/O 库启动的线程调用 socket 并获得与您 closed 相同的描述符。 最后,线程调用recvfrom,现在它从库打开的套接字接收。

哎呀。

永远不会做任何像这样的事情。当另一个线程正在或可能正在使用该资源时,不得释放该资源。期间。

【讨论】:

以上是关于套接字关闭时取消阻止 recvfrom的主要内容,如果未能解决你的问题,请参考以下文章

套接字编程阻塞程序

【奇】udp的recvfrom

在关闭其关联的套接字时取消侦听任务

将 recvfrom() 与原始套接字一起使用:一般疑问

如何知道是啥阻止了 NodeJS 关闭

Winsock编程基础2(UDP流程)