TCP连接的建立、通信及断开的异常情况分析

一、机器准备

  本次实验使用两台机器,一台作为服务端,一台作为客户端,运行linux系统。 

  服务端 192.168.12.36
  客户端 192.168.12.37
  两台机器各自运行某模块服务,两模块为上下游关系,可以进行通信。
  TCP三次握手和四次挥手过程如下图:
  
 
 
二、模拟TCP 第⼀次握⼿ SYN 丢包 
  为了模拟 TCP 第⼀次握⼿ SYN 丢包的情况,可以在拔掉服务器的⽹线后,⽴刻在客户端给服务端发送请求。
  也可以粗暴地在服务端设置iptables规则,使其丢掉来自客户端的所有请求
  iptables -A INPUT -s 192.168.12.36  -j DROP
  然后在客户端执行tcmdump,抓取访问服务端的数据包
  tcpdump tcp port xxx and host 192.168.12.37 -w tcp_sys_timeout.pcap
  接着,把 tcp_sys_timeout.pcap ⽂件⽤ Wireshark 打开并进行分析。
 
  总结:通过实验结果,我们可以得知,当客户端发起的 TCP 第⼀次握⼿ SYN 包,在超时时间内没收到服务端的
ACK,就会在超时重传 SYN 数据包,每次超时重传的 RTO 是翻倍上涨的,直到 SYN 包的重传次数到达
tcp_syn_retries (/proc/sys/net/ipv4/tcp_syn_retries)值后,客户端不再发送 SYN 包。

 

三、模拟TCP 第⼆次握⼿ SYN、ACK 丢包
  为了模拟客户端收不到服务端第⼆次握⼿ SYN、ACK 包,我的做法是在客户端加上防⽕墙限制,直接粗暴的把来
⾃服务端的数据都丢弃,防⽕墙的配置如下:
  iptables -I INPUT -s 192.168.12.36  -j DROP
  接着在客户端发送请求给服务端,并在客户端使用tcpdump命令抓取数据包,然后使用 Wireshark 进行分析。
  
  总结:通过实验结果,我们可以得知,当 TCP 第⼆次握⼿ SYN、ACK 包丢了后,客户端 SYN 包会发⽣超时重
传,服务端 SYN、ACK 也会发⽣超时重传。
  客户端 SYN 包超时重传的最⼤次数,是由 tcp_syn_retries (/proc/sys/net/ipv4/tcp_syn_retries)决定的,默认值是 5 次;服务端 SYN、ACK 包时重传的最⼤次数,是由 tcp_synack_retries(/proc/sys/net/ipv4/tcp_synack_retries) 决定的,默认值是 5 次。
 
四、模拟TCP 第三次握⼿ ACK 丢包
  为了模拟 TCP 第三次握⼿ ACK 包丢,我的实验⽅法是在服务端配置防⽕墙,屏蔽客户端 TCP 报⽂中标志位是
ACK 的包,也就是当服务端收到客户端的 TCP ACK 的报⽂时就会丢弃,iptables 配置命令如下:
  iptables -I INPUT -s 192.168.12.37 -p tcp  --tcp-flag  ACK   ACK  -j DROP
  然后在客户端执行tcpdump抓取数据包,在客户端给服务端发送请求。
  此时,由于服务端收不到第三次握⼿的 ACK 包,所以⼀直处于 SYN_RECV 状态;
  ⽽客户端是已完成 TCP 连接建⽴,处于 ESTABLISHED 状态;
  过了 1 分钟后,观察发现服务端的 TCP 连接不⻅了;
  持续「好⻓」⼀段时间,客户端才断开连接。
 
  总结:在建⽴ TCP 连接时,如果第三次握⼿的 ACK,服务端⽆法收到,则服务端就会短暂处于 SYN_RECV 状态,⽽
客户端会处于 ESTABLISHED 状态。
  由于服务端⼀直收不到 TCP 第三次握⼿的 ACK,则会⼀直重传 SYN、ACK 包,直到重传次数超过
tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。
  ⽽客户端则会有两种情况:
  如果客户端没发送数据包,⼀直处于 ESTABLISHED 状态,然后经过 2 ⼩时 11 分 15 秒(触发了保活机制)才可以发现⼀个
【死亡】连接,于是客户端连接就会断开连接。
  如果客户端发送了数据包,⼀直没有收到服务端对该数据包的确认报⽂,则会⼀直重传该数据包,直到重传次数超过 tcp_retries2 值(/proc/sys/net/ipv4/tcp_retries2,默认值 15 次)后,客户端就会断开 TCP 连接。
 
五、模拟TCP 三次握手后丢包
  这种情况就比较简单了,如果需要服务端丢包,可以在服务端使用gdb -p  xxx(进程号)阻塞住服务端进行即可。客户端同理。
六、关闭连接的两种方式 
  关闭连接的⽅式通常有两种,分别是 RST 报⽂关闭和 FIN 报⽂关闭。
  如果进程异常退出了,内核就会发送 RST 报⽂来关闭,它可以不⾛四次挥⼿流程,是⼀个暴⼒关闭连接的⽅式。
  安全关闭连接的⽅式必须通过四次挥⼿,它由进程调⽤ close 和 shutdown 函数发起 FIN 报⽂(shutdown 参数须传⼊ SHUT_WR 或者 SHUT_RDWR 才会发送 FIN)。
 
  调⽤ close 函数和 shutdown 函数有什么区别?

  调⽤了 close 函数意味着完全断开连接,完全断开不仅指⽆法传输数据,⽽且也不能发送数据。 此时,调⽤了

close 函数的⼀⽅的连接叫做「孤⼉连接」,如果你⽤ netstat -p 命令,会发现连接对应的进程名为空。

  使⽤ close 函数关闭连接是不优雅的。于是,就出现了⼀种优雅关闭连接的 shutdown 函数,它可以控制只关闭

⼀个⽅向的连接:int  shutdown(int sock, int howto);

  第⼆个参数决定断开连接的⽅式,主要有以下三种⽅式:

  SHUT_RD(0):关闭连接的「读」这个⽅向,如果接收缓冲区有已接收的数据,则将会被丢弃,并且后续再收到新的数据,会对数据进⾏ ACK,然后悄悄地丢弃。也就是说,对端还是会接收到 ACK,在这种情况下根本不知道数据已经被丢弃了。

  SHUT_WR(1):关闭连接的「写」这个⽅向,这就是常被称为「半关闭」的连接。如果发送缓冲区还有未发送的数据,将被⽴即发送出去,并发送⼀个 FIN 报⽂给对端。

  SHUT_RDWR(2):相当于 SHUT_RD 和 SHUT_WR 操作各⼀次,关闭套接字的读和写两个⽅向。

  close 和 shutdown 函数都可以关闭连接,但这两种⽅式关闭的连接,不只功能上有差异,控制它们的 Linux 参数也不相同。

七、第一次挥手丢失了,会发生什么?

  当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2 状态。

  如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。

  当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,直接进入到 CLOSE状态。

八、第二次挥手丢失了,会发生什么?

    当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。

  由于ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。

  这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 FIN 报文。

  对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。这意味着对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭。

九、第三次挥手丢失了,会发生什么?

   当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。

  服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。

  如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。

十、第四次挥手丢失了,会发生什么?

  当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。在 Linux 系统,TIME_WAIT 状态会持续 60 秒(也就是2MSL)后才会进入关闭状态。

  然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。

  如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。

posted @ 2022-10-10 13:31  justloving  阅读(1348)  评论(0编辑  收藏  举报