【问题标题】:What is the benefit of using non-blocking sockets with the "select" function?使用带有“选择”功能的非阻塞套接字有什么好处?
【发布时间】:2012-10-07 21:04:20
【问题描述】:
我正在 Linux 中编写一个服务器,它必须支持来自多个客户端的同时读/写操作。我想使用 select 功能来管理读/写可用性。
我不明白的是:假设我想等到套接字有数据可供读取。 select 的文档声明它会阻塞直到有数据可供读取,并且读取函数不会阻塞。
所以如果我使用 select 并且我知道 read 函数不会阻塞,为什么我需要将我的套接字设置为非阻塞?
【问题讨论】:
标签:
sockets
unix
network-programming
【解决方案1】:
在某些情况下,套接字报告为就绪,但当您检查它时,它会更改其状态。
一个很好的例子是接受连接。当一个新的连接到达时,一个监听套接字被报告为准备好读取。当您调用accept 时,连接可能在发送任何内容之前以及在我们调用accept 之前被另一方关闭。当然,这种情况的处理是依赖于操作系统的,但accept 可能会简单地阻塞,直到建立新的连接,这将导致我们的应用程序无限期地等待阻止其他套接字的处理。如果您的侦听套接字处于非阻塞模式,则不会发生这种情况,您会收到EWOULDBLOCK 或其他错误,但accept 无论如何都不会阻塞。
一些内核曾经(我希望现在已经修复)一个有趣的 UDP 和 select 错误。当一个数据报到达时select 唤醒套接字,数据报被标记为准备好读取。数据报校验和验证被推迟到用户代码调用recvfrom(或其他一些能够接收UDP数据报的API)。当代码调用recvfrom 并且验证代码检测到校验和不匹配时,数据报被简单地丢弃,recvfrom 最终被阻塞,直到下一个数据报到达。可以在here 找到修复此问题的补丁程序之一(以及问题描述)。
【解决方案2】:
除了其他人提到的内核错误之外,选择非阻塞套接字(即使使用轮询循环)的另一个原因是它可以在快速到达的数据上实现更高的性能。想想当阻塞套接字被标记为“可读”时会发生什么。您不知道有多少数据已到达,因此您只能安全地读取一次。然后你必须回到事件循环让你的轮询器检查套接字是否仍然可读。这意味着对于套接字的每次读取或写入,您必须至少执行两个系统调用:select 告诉您读取是安全的,以及读取/写入调用本身。
使用非阻塞套接字,您可以在第一次调用之后跳过对select 的不必要调用。当一个套接字被select 标记为可读时,只要它返回数据,您就可以选择从中读取,这样可以更快地处理快速突发的数据。
【解决方案3】:
这听起来很刺耳,但事实并非如此。使它们成为非阻塞的最好理由是不要阻塞。
考虑一下。 select() 告诉你有东西要读,但你不知道有多少。可能是 2 个字节,可能是 2,000。在大多数情况下,在返回select 之前耗尽所有数据会更有效。所以你进入一个while循环来读取
while (1)
{
n = read(sock, buffer, 200);
//check return code, etc
}
如果没有内容可读,最后一次读取会发生什么?如果套接字不是非阻塞的,您将阻塞,从而(至少部分地)击败select() 的要点。