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

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

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

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

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

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

【问题讨论】:

    标签: c sockets select recv


    【解决方案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 的行为是可预测的。

    考虑以下几点:

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

    哎呀。

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

    【讨论】:

      猜你喜欢
      • 2015-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多