主动调用close()发送FIN的一方,收到对方ack及FIN报后状态变为TIME_WAIT,TCP协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分段最大生存期,默认MSL为2分钟),处于TIME_WAIT状态的连接占用的资源不会被内核释放,对应端口默认不能使用,所以作为服务器尽量不要主动断开连接,以减少TIME_WAIT状态造成的资源浪费。BSD、windows系统下TIME_WAIT状态的连接是可以继续建链的。
TIME_WAIT的作用:
1)主动close的一方不能立即关闭,必须确保最后的ack能成功到达对端,2MSL内不需要重传说明最后一个ack已经到达对端;
2)主动close的端口不能立即回收利用,考虑到网络上可能还有一些延时、重传包,立即使用该端口可能收到这些并不需要的包,2MSL时间后这些包就不存在了。
一般客户端发送FIN后,2MSL时间内端口不能用一般不影响使用。服务端同时存在大量链路,TIME_WAIT会耗尽socket资源,因此服务端主动断链时可以发送RST,而不是FIN;或者服务端配置端口可重用。
配置linux端口重用的方法
net.ipv4.tcp_tw_reuse = 1 ----开启快速重用,允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0
net.ipv4.tcp_timestamps = 1 -----启用时间戳,时间戳和seq可以唯一确认tcp报文
开启快速重用功能,重用TIME_WAIT的端口可能收到已关闭链路的乱码。
另外一种方法,可以配置端口快速回收:
net.ipv4.tcp_tw_recycle = 1 ------开启TCP连接中TIME-WAIT sockets的快速回收,默认为0
net.ipv4.tcp_timestamps = 1 -----启用时间戳,时间戳和seq可以唯一确认tcp报文
表示开启快速回收功能后, 可能出现ack丢失,无法重传。
上面配置的修改方法在其他文章描述过,可以修改/etc/sysctl.conf并执行sysctl -p生效;也可以直接生效/sbin/sysctl -p net.ipv4.tcp_tw_recycle = 1;还可以修改/proc/sys/net/ipv4/tcp_tw_recycle文件临时生效。
对端调用close()断开连接,本机的tcp层会主动应答ack,之后本机socket进入CLOSE_WAIT状态,此时本机需要调用close(),才会发送“FIN”包,释放整个链路。出现这样的链路说明没有及时close链路。
表示服务器端的某个SOCKET处于监听状态,可以接受连接。
服务端收到SYN报文后进入该状态,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,理论上这样的状态很少见,但在网络异常状态就很容易出现(参考另一篇博文)。在这种状态时,收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
这个状态与SYN_RCVD呼应,客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
ESTABLISHED:表示连接已经建立了。
FIN_WAIT_1和FIN_WAIT_2状态都是表示等待对方的FIN报文。FIN_WAIT_1状态实际上是主动关闭连接一方,向对方发送了FIN报文进入到FIN_WAIT_1状态。对方回应ACK报文后,则进入到FIN_WAIT_2状态,FIN_WAIT_2状态比FIN_WAIT_1持续时间要长。
FIN_WAIT_2状态下的SOCKET为半连接状态,可以接收数据,不能发送数据。此时对端应该是CLOSE_WAIT状态。
正常情况下,主动关闭一方发送FIN报文后,应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。CLOSING状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。只有双方几乎在同时close一个SOCKET的话,同时发送FIN报文的情况,会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
等待关闭状态。一个SOCKET接收到FIN报文,先回应一个ACK报文给对方,则进入到CLOSE_WAIT状态。此时对端处于FIN_WAIT_2状态,本SOCKET发送完数据后应该立即关闭连接。
被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
1) 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你未必会马上会关闭SOCKET,你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
2) 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态;但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。2MSL时间后可以确认网络上没有需要继续接收的报文,关闭的端口可以安全的重用了。
3) 两个节点,一端显示establish,另一端显示没有这个链路
项目调试时出现过这样的情况,当时没搞清楚,现在明白建链的3次握手过程是可能出现这个情况的。
客户端收到syn+ack后,客户端就是establish,服务端是syn_sent半连接状态。----这个原因多半是网络原因导致握手的第3步ack没有正常接收到。
如果服务端应用accept不够快,accept连接超过队列上限(配置tcp_abort_on_overflow会导致后续建链请求回复RST),半连接队列的链接收到ack无法从半连接队列移动到accept队列;这时,服务端tcp协议栈会定时重复发送syn+ack,客户端一直是establish且正常发送数据(数据都没有ack则重发);
客户端认为服务端不可用,则发送FinAck,进入Fin_Wait1;
服务端还在建链握手过程不响应Fin包,此时accept队列出现空闲,则该链路进入establish;
最后,客户端链路完全结束,而服务端链路为establish状态。----出现这个的原因是服务端没及时处理accept连接,如果确实无法处理了,应该配置sysctl_tcp_abort_on_overflow及时断掉后续请求。