Socket中的TIME_WAIT状态
在高并发短连接的server端,当server处理完client的请求后立刻closesocket此时会出现time_wait状态然后如果client再并发2000个连接,此时部分连接就连接不上了,用linger强制关闭可以解决此问题,但是linger会导致数据丢失,linger值为0时是强制关闭,无论并发多少多能正常连接上,如果非0会发生部分连接不上的情况!(可调用setsockopt设置套接字的linger延时标志,同时将延时时间设置为0。)
TCP/IP的RFC文档。TIME_WAIT是TCP连接断开时必定会出现的状态。
TCP要保证在所有可能的情况下使得所有的数据都能够被投递。当你关闭一个socket时,主动关闭一端的socket将进入TIME_WAIT状态,而被动关闭一方则转入CLOSED状态,这的确能够保证所有的数据都被传输。当一个socket关闭的时候,是通过两端互发信息的四次握手过程完成的,当一端调用close()时,就说明本端没有数据再要发送了。这好似看来在握手完成以后,socket就都应该处于关闭CLOSED状态了。但这有两个问题,首先,我们没有任何机制保证最后的一个ACK能够正常传输,第二,网络上仍然有可能有残余的数据包(wandering duplicates),我们也必须能够正常处理。
1、 应用进程(active close)首先调用close,于是导致TCP发送一个FIN分节,表示数据已分送完毕,请求关闭套接字。
2、 另一端应用进程(passive close)接受收到FIN,并由该端的TCP确认(确认的过程是TCP发送ACK分节给对端套接字)。FIN的接受也作为文件结束符传递给上层应用进程。这里的文件结束符并非应用进程的EOF,在TCP字节流中,EOF的读或写通过收发一个特殊的FIN分节来实现。
3、 另端(passive close)应用进程在接受到文件束符后,会调用close关闭它的套接字,这导致该端的TCP也发送了一个FIN分节。
4、 主动关闭端(active close)接受到这个FIN后,TCP对它进行确认。(TCP发送ACK分节,值得注意的是主动关闭端在未接受到FIN之前,它的状态就是TIME_WAIT)。
综上所述:TIME_WAIT状态出现场景是主动关闭端在未接受到FIN之前,它的状态就是TIME_WAIT。
二.TCP为什么如此设计
1。防止上一次连接中的包(特别是最后一个ACK包),迷路后重新出现,影响新连接 (经过2MSL(max segment lifetime),上一次连接中所有的重复包都会消失)。
2。可靠的关闭TCP连接 在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发
fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以 主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。TIME_WAIT 并不会占用很大资源的,除非受到攻击。还有,如果一方 send 或 recv 超时,就会直接进入 CLOSED 状态
time_wait 处理方法
设置SO_LINGER为零(亦即linger结构中的l_onoff域设为非零,但l_linger为0),便不用担心closesocket调用进入“锁定”状态(等待完成),不论是否有排队数据未发送或未被确认。这种关闭方式称为“强行关闭”,因为套接字的虚电路立即被复位,尚未发出的所有数据都会丢失。在远端的recv()调用都会失败,并返回WSAECONNRESET错误。
在connect成功建立连接之后设置该选项:
linger m_sLinger;
m_sLinger.l_onoff = 1; // (在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
m_sLinger.l_linger = 0; // (容许逗留的时间为0秒)
setsockopt(sockConnected,
SOL_SOCKET,
SO_LINGER,
(const char*)&m_sLinger,
sizeof(linger));
总结
也许我们避免不了CLOSE_WAIT状态冻结的再次出现,但我们会使影响降到最小,希望那个重用套接字选项能够使得下一次重新建立连接时可以把CLOSE_WAIT状态踢掉。