UNIX网络编程——非阻塞式 I/O
概述
套接字的默认状态是阻塞的。
可能阻塞的套接字调用可分为以下四类:
- (1)输入操作,包括 read 、readv、recv、recvfrom 和 recvmsg 共5个函数。
如果某个进程对一个阻塞的TCP套接字(默认设置)调用这些输入函数之一,而且该套接字的接收缓冲区中没有数据可读,该进程将被投入睡眠,直到有一些数据到达。
如果一个阻塞的UDP套接字的接收缓冲区为空,对它调用输入函数的进程将被投入睡眠,直到有UDP数据报到达。 - (2)输出操作,包括 write、writev、send、sendto 和 sendmsg 共5个函数。
对于一个TCP套接字,内核将从应用进程的缓冲区到该套接字的发送缓冲区复制数据。对于阻塞的套接字,如果其发送缓冲区中没有空间,进程将被投入睡眠,直到有空间为止。
对于一个非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,输出函数调用将立即返回一个 EWOULDBLOCK 错误。
如果其发送缓冲区中有一些空间,返回值将是内核能够复制到该缓冲区中的字节数。这个字节数也称为不足计数。
对一个阻塞的UDP套接字(默认设置),输出函数调用将不会因与TCP套接字一样的原因而阻塞,不过有可能会因其他的原因而阻塞。 - (3)接受外来连接,即 accept 函数。
如果对一个阻塞的套接字调用 accept 函数,并且尚无新的连接到达,调用进程将被投入睡眠。 - (4)发起外出连接,即用于 TCP 的 connect 函数。
TCP的每个 connect 总会阻塞其调用进程至少一个到服务器的RTT时间。
非阻塞读和写:str_cli 函数
我们维护着两个缓冲区:to 容纳从标准输入到服务器去的数据,fr 容纳自服务器到标准输出来的数据。
下图展示了to缓冲区的组织和指向该缓冲区中的指针。
下图展示了fr缓冲区相应的组织。
非阻塞 connect
非阻塞的 connect 有三个用途。
-
(1)我们可以把三路握手叠加在其他处理上。
完成一个 connect 要花一个RTT时间,而RTT波动范围很大,从局域网上的几个毫秒到几百个毫秒甚至是广域网上的几秒。 -
(2)我们可以使用这个技术同时建立多个连接。
这个用途已随着Web浏览器变得流行起来。 -
(3)既然使用 select 等待连接的建立,我们可以给 select 指定一个时间限制,使得我们能够缩短 connect 的超时。
非阻塞 connect 有一些我们必须处理的细节。
- 尽管套接字是非阻塞的,如果连接到的服务器在同一个主机上,那么当我们调用 connect 时,连接通常立即建立。我们必须处理这种情形。
- 源自 Berkeley 的实现(和 POSIX)有关于 select 和非阻塞 connect 的以下两个规则:(1)当连接成功建立时,描述符变为可写;(2)当连接建立遇到错误时,描述符变为既可读又可写。
非阻塞 accept
当有一个已完成的连接准备好被 accept 时, select 将作为可读描述符返回该连接的监听套接字。
如果我们使用 select 在某个监听套接字上等待一个外来连接,那就没有必要把该监听套接字设置为非阻塞,这是因为如果 select 告诉我们该套接字上已有连接就绪,那么随后的 accept 调用不应该阻塞。
当使用select 获悉某个监听套接字上何时有已完成连接准备好被 accept 时,总是把这个监听套接字设置为非阻塞。
在后续的 accept 调用中忽略以下错误:EWOULDBLOCK (源自 Berkeley的实现,客户中止连接时)、ECONNABORTED(POSIX实现,客户中止连接时)、EPROTO(SVR4实现,客户中止连接时)和 EINTR (如果有信号被捕获)。
学习参考资料:
《UNIX网络编程 卷1:套接字联网API》 第3版