本文关注内容:TCP连接的TIME_WAIT状态。TCP很重要的一个特性就是可靠性,这是在TCP协议的设计中考虑的重点。TIME_WAIT状态的引入,也是在为TCP可靠性做贡献。
为认识TIME_WAIT状态最基本的设计初衷,我们引一个场景:
在四次挥手阶段,主动关闭方接受被动关闭方的FIN包,然后发送给被动关闭方ACK。此时如果协议策略是直接关闭
连接,清理连接占用的资源。这样会出现问题:如果最后的ACK包丢失了,被动关闭方等待接受ACK超时,就要求重
传。此时主动关闭方已经没有了那个连接的信息,所以发送一个RST。被动关闭方收到RST,一脸茫然。。。
所以,主动关闭方在发给对端ACK包时,不能直接关闭连接,需要保证对端能够收到ACK包,必要时能够重传ACK包。此时主动关闭方所处的状态就是所谓的“TIME_WAIT”状态。
什么是TIME_WAIT状态
TIME_WAIT
- 发生对象:TCP连接的主动关闭方(如果双方同时关闭,则都会进入TIME_WAIT状态)
- 发生时机:四次挥手阶段,主动关闭方发出对被动关闭方FIN报文的ACK报文后。
- 持续时长:2 * MSL (MSL:报文的最大生存时间,伯克利实现设置为30s)
TIME_WAIT的作用(用以解决什么问题)
文章开头我们提到一个TIME_WAIT的好处:(1)主动关闭方进入TIME_WAIT状态确保被动关闭方能够接收到FIN的ACK。除此之外,能够保证:(2)旧连接断开,一个一摸一样的新链接建立(源端口,源IP,目的端口,目的IP都一样),旧连接的数据包不会影响到新的连接。因为TIME_WAIT持续的时间,能够保证数据包在网络中消逝,而连接处于TIME_WAIT状态期间,主机不能使用当前的IP和端口组合建立连接。
TIME_WAIT有何负面影响
TIME_WAIT状态需要持续2*MSL,长达1分钟,甚至更长。在这期间,该连接的端口不能用于建立新的连接。如果短时间内,关闭大量的连接,就会造成大量的TIME_WAIT。
两方面的影响:
- 大量处于TIME_WAIT的连接,消耗主机内存资源(内核表示tcp的tuple)。
- TCP协议用于表示端口的字段为16bit,也就是说,理论上最多可使用65535个端口号,实际上可使用的更少。如果短时间内大量的TIME_WAIT,端口耗尽,就无法再发起新的连接了。
如何避免TIME_WAIT状态
解决:
- TCP选项:TCP_REUSEADDR (强制可以重新使用!)
- 参数:net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle(直接就不在TIME_WAIT状态呆着。。。)
- 连接池(不着急关闭)
TCP_REUSEADDR : 处于TIME_WAIT状态的socket是占有连接的,即不允许另一个socket绑定相同的ip和端口。TCP_REUSEADDR的作用就是:”允许处理TIME_WAIT的连接所占有的IP+端口组合得到复用”。不管之前的socket是否设置了该选项,只要当前的socket设置即可生效。
net.ipv4.tcp_tw_reuse = 1允许在系统重用处于TIME_WAIT连接的端口,作用类似与TCP_REUSEADDR;net.ipv4.tcp_tw_recycle = 1是启用TIME_WAIT快速回收,意味直接就不进入TIME_WAIT状态,靠时间戳区分是旧连接还是新连接的数据包。该功能在NAT环境下可能会出现严重问题。
连接池: 顾名思义,我们使用池子收集空闲的连接,而不是关闭它。这样就能减少TIME_WAIT状态的产生。这种解决方法适合特定的场景,比如数据库连接池。
本文完。