TCP——四次挥手

TCP四次挥手流程

用中文来描述下这个过程:

Client(可以是客户端,也可以是服务器端): 服务端大哥,我事情都干完了,准备撤了,这里对应的就是客户端发了一个FIN,然后进入 FIN-WAIT-1 状态

Server:知道了,但是你等等我,我还要收收尾,这里对应的就是服务端收到 FIN 后回应的 ACK

经过上面两步之后,服务端就会处于 CLOSE_WAIT 状态,客户端处于 FIN-WAIT-2 状态。过了一段时间 Server 收尾完了

Server:小弟,哥哥我做完了,撤吧,服务端发送了FIN,然后进入 LAST-ACK 状态

Client:大哥,再见啊,这里是客户端对服务端的一个 ACK,然后进入 TIME-WAIT 状态

Server:再见,我跑路了,服务端收到 ACK ,然后进入 CLOSED 状态

Client:等待中...,必须等待 2MSL 个时间,然后进入 CLOSED 状态

为什么要四次挥手

TCP连接是双向传输的对等的模式(全双工模式),就是说双方都可以同时向对方发送或接收数据。这就意味着:

当 Client 发出FIN报文段时,只是表示 Client 已经没有数据要发送了,Client 告诉 Server,它的数据已经全部发送完毕了;但是,这个时候 Client 还是可以接受来自 Server 的数据;

当 Server 返回ACK报文段时,表示它已经知道 Client 没有数据发送了,但是 Server 还是可以发送数据到 Client 的;

当 Server 也发送了FIN报文段时,这个时候就表示 Server 也没有数据要发送了,就会告诉 Client ,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。

得出的结论:

当有一方要关闭连接时,会发送指令告知对方,我要关闭连接了,这时对方会回一个ACK,此时一个方向的连接关闭。

但是另一个方向仍然可以继续传输数据,等到发送完了所有的数据后,会发送一个FIN段来关闭此方向上的连接,接收方发送ACK确认关闭连接。

注意,接收到FIN报文的一方只能回复一个ACK, 它是无法马上返回对方一个FIN报文段的,因为结束数据传输的“指令”是上层应用层给出的,我只是一个“搬运工”,我无法了解上层的意志

重要状态

1. CLOSE_WAIT

  • CLOSE_WAIT出现时机与原因
出现时机:
TCP 连接断开时需要进行“四次挥手”,TCP 连接的两端都可以发起关闭连接的请求,若其中一端发起了关闭连接,但另外一端没有关闭连接那么该连接就会处于 CLOSE_WAIT 状态
 
出现原因:
通常来说,CLOSE_WAIT 在服务器停留的时间很短,且只会发生在被动关闭连接的一端。除非 Kill 掉进程,否则它是不会消失的,意味着一直占用资源。
如果发现有大量的 CLOSE_WAIT,那就是被动关闭的一方没有及时发送 FIN(根本原因是没有关闭连接),一般来说有以下几种可能:
  1. 代码问题:请求的时候没有显式关闭 Socket 连接,或者死循环导致关闭连接的代码没有执行到,即 FIN 包没有发出,导致 CLOSE_WAIT 不断累积
  2. 响应过慢 / 超时设置过小:双方连接不稳定,一方 Timeout,另外一方还在处理逻辑,导致 Close 被延后
  • CLOSE_WAIT排查方法

1、查看网络连接

查看 TCP 连接中各个状态数量可以使用以下命令:
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

查看 TCP 连接中 CLOSE_WAIT 的数量可以使用以下命令:

netstat -antop | grep CLOSE_WAIT | wc -l

通过以上命令可以看到服务器上的 CLOSE_WAIT 将近 1w2,这是个不容小觑的潜在问题。

2、梳理 TCP 连接流向

接着我们可以从上图梳理出 CLOSE_WAIT 的连接流向,命令返回里中的 Foreign Address(第 5 栏)代表对方的 IP 地址,即和我们连接着但是却主动关闭了连接的机器。

3、根据项目数据请求流向还原可能场景

然后我们可以根据项目数据请求流向,还原出可能的场景,在我这里即是 CLOSE_WAIT 都发生在本机爬虫、代理以及目标网站的连接上。

毕竟 Program name(最后一栏)都写着 Python 了,且这台服务器上只有爬虫用的 Python。

  • CLOSE_WAIT解决思路

 ..................

2. TIME_WAIT

  • TIME_WAIT出现原因与查看

出现时机:

TIME_WAIT永远是出现在主动发送断开连接请求的一方(下文中我们称之为客户端)。

客户端在收到服务器端发送的FIN(表示"我们也要断开连接了")后发送ACK报文,并且进入TIME_WAIT状态,等待2MSL(MaximumSegmentLifetime 最大报文生存时间)。
对于Linux,字段为TCP_TIMEWAIT_LEN硬编码为30秒,对于windows为2分钟(可自行调整)。
 

等待2ML原因:

为什么客户端不直接进入CLOSED状态,而是要在TIME_WAIT等待那么久呢,基于如下考虑:
 
  1. 确保远程端处于关闭状态。也就是说需要确保客户端发出的最后一个ACK报文能够到达服务器。
      由于网络不可靠,有可能最后一个ACK报文丢失,如果服务器没有收到客户端的ACK,则会重新发送FIN报文,客户端就可以在2MSL时间段内收到这个这个重发的报文,并且重发ACK报文。
           但如果客户端跳过TIME_WAIT阶段进入了CLOSED,服务端始终无法得到响应,就会处于LAST-ACK状态,此时假如客户端发起了一个新连接,则会以失败告终。
 
       2. 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失),这一点和为啥要执行三次握手而不是两次的原因是一样的。
 

排查方法:

1)ss -tan state time-wait|wc -l

(2)netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
  • TIME_WAIT的危害

1.占用连接资源

TIME_WAIT占用的1分钟时间内,相同四元组(源地址,源端口,目标地址,目标端口)的连接无法创建,
通常一个ip可以开启的端口为net.ipv4.ip_local_port_range指定的32768-61000,如果TIME_WAIT状态过多,会导致无法创建新连接。

2.占用内存资源

这个占用资源并不是很多,可以不用担心。

  • TIME_WAIT的解决

可以考虑如下方式:

1.增加可用端口范围(修改net.ipv4.ip_local_port_range); 增加服务端口,

比如采用80,81等多个端口提供服务; 增加客户端ip(适用于负载均衡,比如nginx,采用多个ip连接后端服务器); 增加服务端ip; 这些方式治标不治本,只能缓解问题。

2.将net.ipv4.tcp_max_tw_buckets设置为很小的值(默认是18000). 当TIME_WAIT连接数量达到给定的值时,所有的TIME_WAIT连接会被立刻清除,并打印警告信息。

但这种粗暴的清理掉所有的连接,意味着有些连接并没有成功等待2MSL,就会造成通讯异常。

3.修改TCP_TIMEWAIT_LEN值,减少等待时间,但这个需要修改内核并重新编译。

4.打开tcp_tw_recycle和tcp_timestamps选项。

5.打开tcp_tw_reuse和tcp_timestamps选项。

 
 
 
引用:
  • https://juejin.cn/post/6844903734300901390
  • https://juejin.cn/post/6844903730874171405
原文地址:https://www.cnblogs.com/caoweixiong/p/14622525.html