我正在Linux中编写一个服务器,它必须支持来自多个客户端的同时读/写操作。我想使用 select 函数来管理读/写可用性。
我不明白的是:假设我想等到套接字有可读数据。 select 的文档说明它会阻塞,直到有可读数据,并且read函数不会阻塞。
因此,如果我使用select并且我知道read函数不会阻塞,为什么我需要将套接字设置为非阻塞?
答案 0 :(得分:6)
可能会出现套接字报告为就绪的情况,但是当您检查套接字时,它会更改其状态。
一个很好的例子是接受连接。当新连接到达时,会报告侦听套接字已准备好进行读取。当你接到呼叫接受时,在发送任何内容之前和我们呼叫accept
之前,连接可能会被另一方关闭。当然,这种情况的处理依赖于操作系统,但accept
可能只会阻塞,直到建立新连接,这将导致我们的应用程序等待无限期的时间,以防止处理其他套接字。如果您的侦听套接字处于非阻止模式,则不会发生这种情况,您将收到EWOULDBLOCK
或其他一些错误,但accept
无论如何都不会阻止。
以前有些内核(我希望现在已经修复)是UDP和select
的一个有趣的错误。当数据报到达时select
唤醒套接字,数据报被标记为准备读取。数据报校验和验证被推迟,直到用户代码调用recvfrom
(或某些其他能够接收UDP数据报的API)。当代码调用recvfrom
并且验证代码检测到校验和不匹配时,简单地丢弃数据报并且recvfrom
最终被阻塞,直到下一个数据报到达为止。可以找到解决此问题的补丁之一(以及问题描述)here。
答案 1 :(得分:3)
除了其他人提到的内核错误之外,选择非阻塞套接字的另一个原因是,即使使用轮询循环,它也可以通过快速到达的数据实现更高的性能。想想当阻塞套接字被标记为“可读”时会发生什么。您不知道有多少数据已到达,因此您只能安全地读取一次。然后你必须回到事件循环让你的轮询器检查套接字是否仍然可读。这意味着对于每次读取或写入套接字,您必须至少进行两次系统调用:select
告诉您可以安全地读取,以及读/写调用本身。
使用非阻塞套接字,您可以在第一个之后跳过对select
的不必要的调用。当套接字被标记为select
可读时,只要它返回数据,您就可以选择从中读取套接字,这样可以更快地处理快速突发的数据。
答案 2 :(得分:2)
其中一个好处是,它会捕获您所做的任何编程错误,因为如果您尝试读取通常会阻止您的套接字,您将获得EWOULDBLOCK。对于套接字以外的对象,确切的api行为可能会发生变化,请参阅http://www.scottklement.com/rpg/socktut/nonblocking.html。
答案 3 :(得分:1)
这听起来很讽刺,但事实并非如此。使它们无阻塞的最好理由是你不要阻止它。
想一想。 select()
告诉你有些东西要读,但你不知道多少。可能是2个字节,可能是2,000个。在大多数情况下,在返回select
之前,更有效地消耗任何数据。所以你输入一个while循环来读
while (1)
{
n = read(sock, buffer, 200);
//check return code, etc
}
当没有什么可读的时候,最后一次阅读会发生什么?如果套接字不是非阻塞的,则会阻塞,从而至少(至少部分地)击败select()
的点。