关于套接字:在 Luasocket 中,在什么情况下即使 select 告诉它可以安全读取,accept 调用也会阻塞?

In Luasocket, under which conditions can an accept call block even after select tells it is safe to read?

Luasocket 的选择函数应该告诉何时可以无阻塞地读取套接字。它显然也可以用来判断服务器套接字何时准备好接受新连接,但是文档给出了以下警告:

Another important note: calling select with a server socket in the receive parameter before a call to accept does not guarantee accept will return immediately. Use the settimeout method or accept might block forever.

在什么情况下即使select告诉它可以安全读取也可以接受块?出于测试目的,有没有办法强制发生此问题?


我不知道他们从哪里得到这个想法。在 20 多年的网络编程中从未见过它。

如果您有多个 select() 线程,当然会发生这种情况,但如果这是预期的,我希望文档会这样说。


这是从已故 W.Richard Stevens 的"Unix 网络编程"第 461-463 页的第三版的第 16.6 节(非阻塞 accept)中总结的。 UNP 可能仍然是最好的网络代码编写教科书。

虽然你可能认为 acceptselect 表示侦听套接字已准备好之后不能阻塞,但 Stevens 描述了一些网络堆栈实现中的竞争条件,它可能导致 accept 无限期阻塞。 (脚注将描述归因于"A.Gierth")。该问题是通过一个回显客户端描述的:

  • 连接到服务器;

  • 在连接的套接字上设置 SO_LINGER 套接字选项;

  • 立即关闭套接字。因为已设置 SO_LINGER 选项,关闭套接字会导致发送 RST(重置)。

  • 现在,让我们假设服务器正在运行,但在负载很重的机器上。修改后的 echo 客户端运行。 TCP 连接导致 select 调用返回并指示有可用的连接。 (请记住,连接实际上已被内核接受并放入接受队列;不需要执行 accept 即可。)

    但是,在执行accept调用之前,服务器代码被进程切换中断,同时,客户端设法完成步骤(2)和(3)。然后内核收到来自客户端的重置,现在连接不再有效。因此,它可能会将其从接受队列中删除。

    所以当服务器代码开始 accept 建立连接时,没有要接受的连接,并且 accept 调用阻塞直到下一个连接,如果有的话。

    上述行为实际上可能不会发生。 POSIX 希望 accept 调用以 ECONNABORTED 失败,即使在接受队列中有另一个可用连接(您还必须记住要处理)。根据史蒂文斯的说法:

    In Section 5.11, we noted that when the client aborts the connection before the server calls `accept`, Berkeley-derived implementations do not return the aborted connection to the server, while other implementations should return `ECONNABORTED` but often return `EPROTO` instead.

    史蒂文斯的源代码可在此发布者的网站上获得;修改后的客户端是 nonblock/tcpcli03.c,对服务器的修改只是在调用 accept 之前hibernate五秒钟。因此,您可以在任何可用的系统上试用它。

    我不相信 FreeBSD 或 Linux 不再表现出伯克利派生的行为,尽管我很确定我记得它发生在 FreeBSD 上(那可能是十多年前,而且我不再有FreeBSD 盒子可以方便地对其进行测试。)OpenBSD 似乎已在 1999 年进行了修补以解决该问题(请参阅 2.4 的补丁);可能其他伯克利衍生物后来也做了类似的改变。我不知道 MacOSX(尽管它可能与 FreeBSD 相同)或 Windows。很可能没有现代系统表现出这种行为,尽管在 Stevens 编写 UNP 时它肯定是可以观察到的。

    无论如何,史蒂文斯的建议很简单,小心一点总不会有坏处。他的建议是:

  • 当你在其上使用 select 时,始终将侦听套接字设置为非阻塞;

  • 如果 acceptEWOULDBLOCKECONNABORTEDEPROTOEINTR 而失败,则忽略错误并返回 select 循环。