我正在 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().