数据传输完毕后,双方都可释放连接,最开始时,客户端和服务器都处于ESTABLISHED状态,这里假设由客户端主动关闭,服务器被动关闭
第一次挥手:
客户端进程发出释放连接报文(FIN=1,序列号seq=u,u=之前传送过来的数据最后一个字节的序号+1),并且停止发送数据。此时,客户端进入FIN-WAIT-1状态。
第二次挥手:
服务端收到来自客户端的释放连接报文后,向客户端发送确认响应报文(ACK=1,ack number=u+1,并且带上自己的序列号seq=v),此时,服务器进入CLOSE-WAIT状态,也就是半关闭状态(客户端已经没有数据要发送了,但如果服务器发送数据,客户端依然要接收),同时会通知应用程序客户端要求关闭连接。这个状态还要持续一段时间,该时间等于服务端的CLOSE-WAIT状态持续的时间。
客户端收到服务端的确认响应报文后,进入FIN-WAIT-2状态,等待服务端发送连接释放的报文(在这之前还需要接收服务器发送最后的数据)。
第三次挥手:
服务器将最后的数据发送完毕后,应用程序通知TCP可以关闭连接,于是向客户端发送释放连接报文(FIN=1,seq=w,ACK=1,ack number = u+1),由于在半关闭状态,服务器很可能又发送了一些数据,此时服务器就进入了LAST-ACK状态,等待客户端确认。
第四次挥手:
客户端收到服务器的释放连接报文后,必须对服务端进行确认响应报文(ACK=1,ack number=w+1,序列号seq=u+1),此时,客户端就进入了TIME-WAIT状态。此时TCP连接还没有释放,必须等待2*MSL(最长报文段寿命)时间后,当客户端撤销响应的TCB后,才进入CLOSED状态。
服务端接收到了客户端的确认响应报文后,会立即进入CLOSED状态,同样撤销TCB后,结束这次TCP连接。由此可见,服务端结束TCP的连接时间要比客户端早一些。
为什么要等待2*MSL
MSL:指一个报文在网络中的最大存活时间,2MSL就是执行一个发送和一个回复所需要的最大时间。
①:确保有足够时间让对端收到ACK包
理想状态下,当四次握手完成后,主动端是可以直接进入CLOSE状态的,但是我们必须假设网络是不可靠的,很有可能在第四次握手时,主动端发送的ACK会丢失。
所以TIME_WAIT状态就是用于主动段重发可能丢失的ACK报文的。在客户端发送出最后的ACK确认报文后,如果服务端未收到该ACK,则会不断发送FIN数据包。所以客户端不能立即关闭,它必须确认服务端的确收到了ACK。
客户端会设置一个计时器等待2MSL,如果在该时段内再次收到FIN,那么客户端会重发ACK并再次等待2MSL。如果直到2MSL都没有再次收到FIN,那么客户端则推断ACK被成功接收,结束TCP连接。
确保有足够的时间让对方收到确认响应报文(ACK),如果被动关闭的一端没有收到ACK,就会触发被动端重发FIN包。
②:避免新旧连接混淆:
在客户端发送ACK报文后,经过2MSL时间,就可以使持续时间中所产生的所有报文段都从网络中消失,这样就可以使下一个新的连接中不会出现这种旧的连接请求的报文段了。
为什么需要四次握手
关闭连接时,当被动端(假设为服务器)收到FIN释放链接报文时(第一次挥手),很可能不会立即关闭SOCKET,所以只能先回复一个ACK确认响应报文(第二次挥手)告知主动端(假设为客户端)自己收到了该FIN报文。当服务端把所有的数据都发送完之后,才能再发送FIN报文(第三次挥手)告知客户端此时可以释放连接了。客户端对该FIN报文必须进行ACK回复(第四次挥手),所以关闭连接时需要四次握手。
服务器大量出现CLOSE-WAIT的原因
当客户端向服务器发送了FIN数据包要求释放连接时,服务器接收后并没有进一步发送ACK,也就是在服务器程序中并没有进行相应的连接关闭,于是就出现了资源一直被占用的情况。
其根本原因很有可能是:
①程序的代码,尤其是释放资源的代码处有bug
②某些配置,如线程池中的线程数配置不合理
Linux下可通过该命令查看TCP连接状态
netstat -n|grep ^tcp | awk ‘{++S[$NF]} END {for(a in S) print a, S[a]}’