TCP之非阻塞connect和accept
套接字的默认状态是阻塞的,这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待响应操作完成,可能阻塞的套接字调用可分为以下四类:
(1) 输入操作,包括read,readv,recv,recvfrom,recvmsg;
(2) 输出操作,包括write,writev,send,sendto,sendmsg;
(3) 接受外来连接,即accept函数。
(4) 发起外出连接,即tcp的connect函数;
非阻塞connect:
当一个非阻塞的tcp套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的tcp三路握手继续进行。我们接着使用select检测这个连接或成功或失败的已建立条件。非阻塞connect有三个用途:
(1) 我们可以把三路握手叠加在其他处理上,完成一个connect要花的RTT时间,而RTT波动很大,从局域网上的几毫秒到几百毫秒甚至是广域网的几秒。这段时间内也许有我们想要执行的其他工作可执行;
(2) 我们可以使用这个技术同时建立多个连接;这个技术随着web浏览器流行起来;
(3) 既然使用select等待连接建立,我们可以给select指定一个时间限制,使得我们能够缩短connect的超时。
非阻塞connect细节:
(1) 尽管套接字是非阻塞的,如果连接到的服务器在同一个主机上,那么当我们调用connect时候,连接通常立刻建立,我们必须处理这种情形;
(2) 源自Berkeley的实现(和posix)有关select和非阻塞connect的以下两个原则:
--(a) 当连接成功建立时,描述符变为可写;
--(b) 当连接建立遇到错误时,描述符变为既可读又可写;
非阻塞accept:
在比较忙的服务器中,在建立三次握手之后,调用accept之前,可能出现客户端断开连接的情况,再这样的情况下;如,三次握手之后,客户端发送rst,然后服务器调用accept。posix指出这种情况errno设置为CONNABORTED;
注意Berkeley实现中,没有返回这个错误,而是EPROTO,同时完成三次握手的连接会从已完成队列中移除;在这种情况下,如果我们用select监听到有新的连接完成,但之后又被从完成队列中删除,此时如果调用阻塞accept就会产生阻塞;
解决办法:
(1) 使用select监听套接字是否有完成连接的时候,总是把这个监听套接字设置为非阻塞;
(2) 在后续的accept调用中忽略以下错误,EWOULDBLOCK(Berkeley实现,客户中止连接), ECONNABORTED(posix实现,客户中止连接), EPROTO(serv4实现,客户中止连接)和EINTR(如果有信号被捕获);