TCP(二)TIME_WAIT
一、 什么是timewait?
Timewait是TCP连接中,四次挥手时出现的一个状态,在主动关闭方发出最后一个ACK后,就会进入timewait状态,并等待2MSL时间后,进入CLOSE状态。
二、 MSL
MSL(Maximum Segment Lifetime),报文最大生存时间,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
在linux操作系统中,是30秒。这个数值是硬编码在内核中的,也就是说除非你重新编译内核,否则没法修改它
客户端发起http请求,并主动关闭后,可以看到linux中2msl时间就是60s,timewait (59.98/0/0)
# curl "http://10.30.20.80"&&netstat -antp -o|grep TIME_WAIT tcp 0 0 10.30.20.91:30078 10.30.20.80:80 TIME_WAIT - timewait (59.98/0/0)
三、 为什么需要timewait状态?
1、 确保被动关闭方已经关闭了连接
当主动关闭方发出最后的ACK后,如果由于某种异常导致报文丢失,被动方没有收到最后的ACK报文会一直处于LAST-ACK状态,无法进入CLOSED状态。
假设主动关闭方跳过TIME_WAIT状态或者处于TIME_WAIT状态很短的时间后进入CLOSED状态,此时主动关闭方如果使用相同的源端口,发起SYN建连请求,被动关闭方由于还处于LAST_ACK状态,收到SYN包,此时就会回复RST包,导致新连接无法正常建立起来。
2、 新的TCP连接被建立起来了,延迟包可能干扰新的连接
当使用原来的五元组来建立新的TCP连接,如果上一次连接还有数据报文,由于网络拥塞等原因,在新连接建立后才到达(且序列号一致),此时就会干扰到新的连接了,当然出现这种问题的概率比较低。
四、 处于Last_ack状态的数据包,什么时候关闭?
假设主动关闭方为A,被动关闭方为B
1、B发送FIN后,进入LAST_ACK状态,A收到FIN包后发送ACK包,B收到这个ACK后,进入CLOSED状态。
2、B发送FIN后,进入LAST_ACK状态,A收到FIN包后发送ACK包,由于某种原因,这个ACK包丢失,B没有收到ACK包,然后B等待ACK包超时,又向A发送了一个FIN包。
1)假设A还处于TIME_WAIT状态,A收到这个FIN包后向B发送了一个ACK包,B收到这个ACK包进入CLOSED状态。
2)假设A已经处于CLOSED状态,A收到这个FIN包后,认为这是一个错误的连接,向B发送一个RST包,当B收到这个RST包,进入CLOSED状态。
3)假设A宕机,B没有收到A的回应,那么会继续发送FIN包,也就是触发了TCP的重传机制,如果A还是没有回应,B还会继续发送FIN包,直到重传超时,B重置这个连接,进入CLOSED状态。
五、timewait优化
timewait优化的可以从如下维度进行调整
1、从五元组的维度
源地址、源端口、目的地址、目的端口、协议
1)源地址
增加客户端的IP地址
2)源端口
设置内核参数net.ipv4.ip_local_port_range,调整可用的端口范围,由于只有16位,最多只能有65535,调整可用的端口范围,1-65535
3)目的地址
增加服务端的IP地址
4)目的端口
增加服务端的监听端口
2、缩短回收时间或重用维度
1)tcp_max_tw_buckets
设置存储的buckets大小,超过指定的大小,将丢弃。从timewait的数量来说,指定了timewait数量的上限,但是为了保证tcp的正常关闭,应该调大
2)tcp_tw_reuse
重用tcp连接,只对连接发起方有效
3)tcp_tw_recycle
缩短timewai的回收时间为3*RTO,但是会有一定几率影响连接建立,生产上建议还是不要开启
六、几个内核参数的说明
1、tcp_timestamp
开启TCP的timestamp的option,两个4字节的时间戳字段,其中第一个4字节字段用来保存发送该数据包的时间,第二个4字节字段用来保存最近一次接收对方发送到数据的时间戳。
2、tcp_tw_recycle
开启后,缩短time_wait的回收时间,回收时间为3*RTO(Retransmission Timeout),RTO 时间在200ms~ 120s 具体时间视网络状况。
# ss --info sport = :64707 dport = :58625 Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp ESTAB 0 0 10.30.20.80:64707 10.31.4.13:58625 cubic wscale:2,7 rto:201 rtt:0.273/0.06 ato:40 mss:1448 rcvmss:1448 advmss:1448 cwnd:10 bytes_acked:9440165 bytes_received:3070 segs_out:14177 segs_in:10034 send 424.3Mbps lastsnd:7478 lastrcv:60707483 lastack:7477 pacing_rate 846.3Mbps reordering:5 rcv_rtt:1 rcv_space:29200
当多个客户端通过NAT方式联网并与服务端交互时,服务端看到的是同一个IP,也就是说对服务端而言这些客户端实际上等同于一个,可惜由于这些客户端的时间戳可能存在差异,于是乎从服务端的视角看,便可能出现时间戳错乱的现象,进而直接导致时间戳小的数据包被丢弃。如果发生了此类问题,具体的表现通常是是客户端明明发送的SYN,但服务端就是不响应ACK,我们可以通过下面命令来确认数据包不断被丢弃的现象。
补充:在测试中,使用在同一局域网的两台服务器之间互访,开启参数后,都有一定的几率由于时间戳导致无法正常建立连接。
客户端:10.30.20.108 服务端:10.30.20.80 测试命令:ab -c 1000 -n 600000 http://10.30.20.80/ 内核版本:3.10.0-1062.9.1.el7.x86_64 场景一、 客户端参数: net.ipv4.tcp_timestamps=1 net.ipv4.tcp_tw_recycle = 0 net.ipv4.tcp_tw_reuse = 0 服务端: net.ipv4.tcp_timestamps=1 net.ipv4.tcp_tw_recycle = 0 net.ipv4.tcp_tw_reuse = 0 结果: # netstat -s|grep "time stamp" # 没有因为时间戳导致的连接建立异常 # ss -s Total: 213 (kernel 406) TCP: 60571 (estab 4, closed 60561, orphaned 0, synrecv 0, timewait 60561/0), ports 0 Transport Total IP IPv6 * 406 - - RAW 0 0 0 UDP 1 1 0 TCP 10 8 2 INET 11 9 2 FRAG 0 0 0 场景二 客户端参数: net.ipv4.tcp_timestamps=1 net.ipv4.tcp_tw_recycle = 0 net.ipv4.tcp_tw_reuse = 0 服务端: net.ipv4.tcp_timestamps=1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 0 结果: # netstat -s|grep "time stamp";ss -s 35 passive connections rejected because of time stamp # 出现时间戳导致的连接建立异常 Total: 215 (kernel 419) TCP: 11 (estab 4, closed 1, orphaned 0, synrecv 0, timewait 1/0), ports 0 Transport Total IP IPv6 * 419 - - RAW 0 0 0 UDP 3 2 1 TCP 10 8 2 INET 13 10 3 FRAG 0 0 0
3、tcp_tw_reuse
tcp_tw_reuse,就是重用处于TIME-WAIT状态的sockets来建立新连接,也就是说在客户端起作用。
重用TIME_WAIT的条件是收到最后一个包后超过1s,就可以开始重用这个socket。