我正在 Linux 中编写一个服务器,它必须支持来自多个客户端的同时读/写操作。我想使用select功能来管理读/写可用性。
我不明白的是:假设我想等到套接字有数据可供读取。select的文档声明它会阻塞,直到有数据可供读取,并且 read 函数不会阻塞。
所以如果我使用 select 并且我知道 read 函数不会阻塞,为什么我需要将我的套接字设置为非阻塞?
我正在 Linux 中编写一个服务器,它必须支持来自多个客户端的同时读/写操作。我想使用select功能来管理读/写可用性。
我不明白的是:假设我想等到套接字有数据可供读取。select的文档声明它会阻塞,直到有数据可供读取,并且 read 函数不会阻塞。
所以如果我使用 select 并且我知道 read 函数不会阻塞,为什么我需要将我的套接字设置为非阻塞?
在某些情况下,套接字被报告为就绪,但当您检查它时,它会更改其状态。
一个很好的例子是接受连接。当一个新的连接到达时,一个监听套接字被报告为准备好读取。当您调用 accept 时,连接可能在发送任何内容之前和我们调用之前被另一方关闭accept
。当然,这种情况的处理是依赖于操作系统的,但它可能accept
会简单地阻塞直到建立新的连接,这将导致我们的应用程序无限期地等待阻止其他套接字的处理。如果您的侦听套接字处于非阻塞模式,则不会发生这种情况,您会得到EWOULDBLOCK
或其他一些错误,但accept
无论如何都不会阻塞。
一些内核曾经(我希望现在已经修复)一个有趣的 UDP 和select
. 当数据报到达select
时,通过套接字唤醒,数据报被标记为准备好读取。数据报校验和验证被推迟到用户代码调用recvfrom
(或其他一些能够接收 UDP 数据报的 API)。当代码调用recvfrom
并且验证代码检测到校验和不匹配时,数据报被简单地丢弃并recvfrom
最终被阻塞,直到下一个数据报到达。可以在此处找到修复此问题的补丁程序之一(以及问题描述) 。
除了其他人提到的内核错误之外,选择非阻塞套接字(即使使用轮询循环)的另一个原因是它允许快速到达数据的更高性能。想想当阻塞套接字被标记为“可读”时会发生什么。您不知道有多少数据已到达,因此您只能安全地读取一次。然后你必须回到事件循环让你的轮询器检查套接字是否仍然可读。这意味着对于套接字的每次读取或写入,您必须至少执行两个系统调用:select
告诉您读取是安全的,以及读取/写入调用本身。
With non-blocking sockets you can skip the unnecessary calls to select
after the first one. When a socket is flagged as readable by select
, you have the option of reading from it as long as it returns data, which allows faster processing of quick bursts of data.
好处之一是它会捕获您犯的任何编程错误,因为如果您尝试读取通常会阻止您的套接字,您将获得 EWOULDBLOCK。对于套接字以外的对象,确切的 api 行为可能会改变,请参阅http://www.scottklement.com/rpg/socktut/nonblocking.html。
这听起来很刺耳,但事实并非如此。使它们成为非阻塞的最佳理由是不要阻塞。
想想看。 select()
告诉你有东西要读,但你不知道有多少。可能是 2 个字节,可能是 2,000。在大多数情况下,在返回select
. 所以你进入一个while循环来阅读
while (1)
{
n = read(sock, buffer, 200);
//check return code, etc
}
当没有什么要读的时候,最后一次读会发生什么?如果套接字不是非阻塞的,您将阻塞,从而(至少部分地)击败select()
.