Luasocket选择函数应该告诉何时可以无阻塞地读取套接字。它显然也可以用来判断服务器套接字何时准备好接受新连接,但是文档给出了以下警告:
另一个重要的注意事项:在调用 accept 之前在接收参数中使用服务器套接字调用 select 并不能保证 accept 会立即返回。使用 settimeout 方法,否则 accept 可能会永远阻塞。
即使 select 告诉它可以安全阅读,在什么情况下可以接受块?出于测试目的,有没有办法强制发生此问题?
我不知道他们从哪里得到这个想法。在 20 多年的网络编程中从未见过它。
如果您有多个 select() 线程,当然会发生这种情况,但是如果这是预期的,我希望文档会这样说。
accept
这是从已故 W.Richard Stevens 的“Unix Network Programming”第 461-463 页的第三版的第 16.6 节(非阻塞)中总结出来的。UNP 可能仍然是最好的关于编写网络代码的教科书。
尽管您可能认为accept
不能阻塞之后select
表明侦听套接字已准备好,但 Stevens 描述了一些网络堆栈实现中的竞争条件,它可能导致accept
无限期阻塞。(脚注将描述归因于“A.Gierth”)。这个问题是通过一个 echo 客户端来描述的,它:
连接到服务器;
在连接的套接字上设置SO_LINGER
套接字选项;
立即关闭套接字。由于SO_LINGER
已设置选项,关闭套接字会导致发送RST
(重置)。
现在,让我们假设服务器正在运行,但在负载很重的机器上。修改后的 echo 客户端运行。TCP 连接会导致select
调用返回并指示有可用的连接。(请记住,连接实际上已被内核接受并放入接受队列;accept
不需要执行此操作。)
但是,在调用执行之前,服务器代码被进程切换中断,accept
同时,客户端设法完成步骤(2)和(3)。然后内核收到客户端的reset,现在连接不再有效。因此,它可能会将其从接受队列中删除。
因此,当服务器代码accept
开始建立连接时,没有要接受的连接,并且accept
调用阻塞直到下一个连接,如果有的话。
上述行为实际上可能不会发生。即使接受队列中有另一个可用连接(您还必须记住要处理),POSIX 也希望accept
调用失败。根据史蒂文斯的说法:ECONNABORTED
在第 5.11 节中,我们注意到当客户端在服务器调用 `accept` 之前中止连接时,Berkeley 派生的实现不会将中止的连接返回给服务器,而其他实现应该返回 `ECONNABORTED` 但通常会返回 `EPROTO` .
史蒂文斯的源代码可在出版商网站上找到;修改后的客户端是nonblock/tcpcli03.c
,而对服务器的修改只是在调用之前休眠五秒钟accept
。因此,您可以在任何可用的系统上进行尝试。
我不相信 FreeBSD 或 Linux 不再表现出伯克利派生的行为,尽管我很确定我记得它发生在 FreeBSD 上(那可能是十多年前的事了,而且我不再手边有一个 FreeBSD 盒子对其进行测试。)OpenBSD 似乎已在 1999 年进行了修补以解决该问题(请参阅2.4 的补丁);可能其他伯克利衍生物后来也做了类似的改变。我不知道 MacOSX(尽管它可能与 FreeBSD 相同)或 Windows。很可能没有现代系统表现出这种行为,尽管当 Stevens 编写 UNP 时它肯定是可以观察到的。
无论如何,史蒂文斯的建议很简单,小心一点总不会有坏处。他的建议是:
使用时始终将侦听套接字设置为非阻塞select
;
如果accept
以EWOULDBLOCK
、ECONNABORTED
、EPROTO
或失败EINTR
,则忽略错误并返回select
循环。