三次握手原因:保证数据传输可靠。通过三次握手对连接双方的序号进行确认,保证未来的可靠数据传输。
四次挥手原因:全双工。
TIME_WAIT:主动关闭端发送最后一个ACK时,需在TIME_WAIT状态停留2MSL时间。(MSL:任何报文段被丢弃前在网络中的最长时间)(该状态下,定义该连接的套接字不能再被使用,即不能建立新连接)
目的:
- 防止最后一个ACK丢失。若丢失,另一端超时重传FIN。假设C端是主动关闭方,C端发送的最后一个ACK丢失,S端超时重传FIN,若无TIME_WAIT,C端则已经断开连接,收到S的FIN时,会返回一个RST报文,则S端会连接异常。(java中为connection reset的SocketException)
- 假设不停留在TIME_WAIT状态,新建了连接,会导致:
2.1 新建连接无法成功。假设C端最后一个ACK超时,但没有TIME_WAIT状态,则C端默认已经关闭了该连接,此时C端复用该套接字,发送SYN新建连接,由于S端未收到上一次连接的ACK,会发送RST报文,新连接无法建立成功,C端异常。(2MSL的作用:1个MSL确保最后一个ACK报文到达对方,一个MSL确保对方超时重传的FIN能够到达)
2.2 旧连接的迟到报文段被误解为是新连接的报文段。(而2MSL的时间足够让本次连接所有迟到报文段都消失在网络中,防止其扰乱新连接)
其他情况假设:
- S端的FIN丢失:S端重传FIN,C端收到发送ACK。
- C端的最后一个ACK连续丢失两次,2MSL时间是否够用?够用,C端每次发送报文计时器会重置,重新开始2MSL时间的计时。
TIME_WAIT过多:主动关闭连接一方可能出现大量TIME_WAIT,端口不够用加上停留时间过长,可能会导致无法接受新的连接。解决方法(编辑文件/etc/sysctl.conf修改参数):
- net.ipv4.tcp_timestamps:两个时间戳字段
TSval为发送该数据包的时间,TSecr只在设置了ACK位时有效,值通常为最近收到对方发送的数据包的时间。
rfc1323 3.2节:The Timestamp Echo Reply field (TSecr) is only valid if the ACK
bit is set in the TCP header; if it is valid, it echos a times-
tamp value that was sent by the remote TCP in the TSval field
of a Timestamps option. When TSecr is not valid, its value
must be zero. The TSecr value will generally be from the most
recent Timestamp option that was received; however, there are
exceptions that are explained below.
- net.ipv4.tcp_tw_reuse:复用处于TIME_WAIT状态的连接。
问题在于,该连接被复用,上面出现的ACK丢失或延迟报文段到达等问题如何解决:
2.1 依赖上述时间戳选项,来判断收到的数据包是否属于旧连接:由于新建连接会收到ACK,则TSecr有效且保存了新建连接的时间,当收到一个数据包时,比较存储的TSecr与该数据包被发送的时间TSval,即可判断该数据包属于哪条连接,若早于TSecr,说明是旧连接的延迟数据包,可以丢弃。
2.2 对于上一次连接最后一个ACK丢失的问题:由于主动关闭方新建了连接,此时就处于SYN_SENT阶段,上一次连接的ACK丢失,则对方会重传FIN,此时主动关闭方收到FIN,会返回一个RST,则被动关闭方断开连接,进入CLOSED状态。而新连接的SYN没收到ACK,会重传一个SYN,这时对方已经是CLOSED状态,可以正常建立连接了。
因此该配置依赖通信双方开启net.ipv4.tcp_timestamps参数。
3. net.ipv4.tcp_tw_recycle:内核快速回收处于TIME_WAIT状态的连接。
(与复用连接不同,只是可能恰好用了相同的五元组)
同样依赖时间戳判断无效的数据包:系统缓存每台主机(IP)连接过来的最新的时间戳,收到一个SYN时,若其S时间戳早于记录的相同主机的同一连接的时间戳,则认为是过期连接,直接丢弃。如果晚于记录的时间戳,则更新该时间戳。
但会出现另一个问题。如果客户端是在NAT网络中(多个客户端向该服务器发送请求,显示为同一个IP出口,(由于设置了tcp_tw_recycle字段,会清除TCP的四元组信息释放内存,因此没法根据端口判断,但可以根据IP判断,即比较该IP的时间戳。)),一个RTO内多个客户端同时建立连接,由于不同客户端发包时间不同,只有一个客户端能成功建立连接(最新发起连接的那个):
在三次握手的第二步,如果服务端发送的SYN ACK报文中的TSecr不等于第一步客户端报文中的TSval,客户端会在第三步返回RST报文,连接建立不成功。
所以客户端在NAT网络中时不要开启该选项。
CLOSE_WAIT:被动关闭方未关闭连接所处的状态。
出现原因:
- 应用程序没有合适的关闭socket
- 获取不到CPU时间片去关闭连接,可能时间片都被其他进程占用了,或者应用程序被阻塞(锁等)
大量CLOSE_WAIT:由于套接字要打开fd,而机器能打开的fd有限,且内核中的hash table要维护连接,因此大量CLOSE_WAIT会占用过多内存和fd。
keepalive:设置SO_KEEPALIVE选项,则TCP的keepalive保活机制生效。
TCP keepalive保活机制的参数(/etc/sysctl.conf中设置):
- tcp_keepalive_time:发送第一个保活探测包的时间间隔(即正常发送心跳的周期),默认7200s即两小时。
- tcp_keepalive_probes:第一个探测包未收到确认,继续发送的次数,默认9次。
- tcp_keepalive_intvl:探测包未收到确认,继续发送的时间间隔,默认75s。
参考:
https://zhuanlan.zhihu.com/p/40013724
https://datatracker.ietf.org/doc/html/rfc1323
http://chenzhenianqing.com/articles/1150.html