TCP三次握手和四次挥手

  • 关注设计原理
  • 关注异常处理

8.1 有关TCP 连接的退出,你该知道的事…(下)

9.1 灵魂拷问 TCP ,你要投降了吗?(菜鸟教程)

TCP 三次握手和四次挥手过程中,途中某一步的报文丢失了,会发生什么?

今天就和大家一起来详细了解下,TCP 三次握手和四次挥手过程中每一步的异常情况。

发车!

在这里插入图片描述

9.1.1 TCP 三次握手丢包情况

第一次握手丢失了,会发生什么?

当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。

在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的

不同版本的操作系统可能超时时间不同,有 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,如果想要更改则需要重新编译内核,比较麻烦。

当客户端在 1 秒后还没收到服务端的 SYN-ACK 报文,客户端就会重发 SYN 报文,那到底重发几次呢?

在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5。

$ cat /proc/sys/net/ipv4/tcp_syn_retries
5

通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒后,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次超时重传是在 16 秒后。没错,每次超时的时间是上一次的 2 倍

当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。

所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。

举个例子,假设 tcp_syn_retries 参数值为 3,那么当客户端的 SYN 报文一直在网络中丢失时,会发生下图的过程:

在这里插入图片描述

具体过程:

当客户端超时重传 3 次 SYN 报文后,由于 tcp_syn_retries 为 3,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。

TCP SYN是什么的缩写
TCP SYN是TCP协议中的一个标志位,其缩写为"SYN",全称为"Synchronize"。在TCP三次握手的过程中,SYN用于建立连接,发送端向接收端发送一个带有SYN标志位的数据包,表示请求建立连接。接收端收到该数据包后,会发送一个带有SYN和ACK标志位的数据包作为应答,表示接收端同意建立连接。

第二次握手丢失了,会发生什么?

当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD 状态。

第二次握手的 SYN-ACK 报文其实有两个目的 :

  • 第二次握手里的 ACK, 是对第一次握手的确认报文;
  • 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文。

所以,如果第二次握手丢了,就会发生比较有意思的事情,具体会怎么样呢?

因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文,所以,如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文

然后,因为第二次握手中包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。

那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文

在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定,默认值是 5。

$ cat /proc/sys/net/ipv4/tcp_synack_retries
5

因此,当第二次握手丢失了,客户端和服务端都会重传:

  • 客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由 tcp_syn_retries内核参数决定;
  • 服务端会重传 SYN-ACK 报文,也就是第二次握手,最大重传次数由 tcp_synack_retries 内核参数决定。

举个例子,假设 tcp_syn_retries 参数值为 1,tcp_synack_retries 参数值为 2,那么当第二次握手一直丢失时,发生的过程如下图:

在这里插入图片描述
具体过程:

  • 当客户端超时重传 1 次 SYN 报文后,由于 tcp_syn_retries 为 1,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。
  • 当服务端超时重传 2 次 SYN-ACK 报文后,由于 tcp_synack_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。

第三次握手丢失了,会发生什么?

客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。

因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。

注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文

举个例子,假设 tcp_synack_retries 参数值为 2,那么当第三次握手一直丢失时,发生的过程如下图:

在这里插入图片描述
具体过程:

  • 当服务端超时重传 2 次 SYN-ACK 报文后,由于 tcp_synack_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。

TCP客户端假连接ESTABLISHED

9.1.2 TCP 四次挥手丢包情况

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

当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文(Finish 结束),试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。

正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2状态。

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

当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。

举个例子,假设 tcp_orphan_retries 参数值为 3,当第一次挥手一直丢失时,发生的过程如下图:

在这里插入图片描述
具体过程:

  • 当客户端超时重传 3 次 FIN 报文后,由于 tcp_orphan_retries 为 3,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次挥手(ACK报文),那么客户端就会断开连接。

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

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

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

举个例子,假设 tcp_orphan_retries 参数值为 2,当第二次挥手一直丢失时,发生的过程如下图:

在这里插入图片描述

具体过程:

  • 当客户端超时重传 2 次 FIN 报文后,由于 tcp_orphan_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次挥手(ACK 报文),那么客户端就会断开连接。

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

对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。

$ cat /proc/sys/net/ipv4/tcp_fin_timeout
30

这意味着对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭,如下图:
在这里插入图片描述
但是注意,如果主动关闭方使用 shutdown 函数关闭连接,指定了只关闭发送方向,而接收方向并没有关闭,那么意味着主动关闭方还是可以接收数据的。

此时,如果主动关闭方一直没收到第三次挥手,那么主动关闭方的连接将会一直处于 FIN_WAIT2 状态(tcp_fin_timeout 无法控制 shutdown 关闭的连接)。如下图:

在这里插入图片描述

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

当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。

此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。

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

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

举个例子,假设 tcp_orphan_retries = 3,当第三次挥手一直丢失时,发生的过程如下图:
在这里插入图片描述

具体过程:

  • 当服务端重传第三次挥手报文的次数达到了 3 次后,由于 tcp_orphan_retries 为 3,达到了重传最大次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。
  • 客户端因为是通过 close 函数关闭连接的,处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。

异常情况:

  • 被动关闭方没有调用close && 不在发数据:主动方 FIN_WAIT2 超时退出,被动方一直处于 CLOSE_WAIT 状态。
  • 被动关闭方没有调用close && 有在发数据:主动方 FIN_WAIT2 超时退出 | 收到数据退出,被动方发数据、收到RST内核直接退出 CLOSE_WAIT 状态。

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

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。

在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。

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

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

举个例子,假设 tcp_orphan_retries 为 2,当第四次挥手一直丢失时,发生的过程如下:

在这里插入图片描述

具体过程:

  • 当服务端重传第三次挥手报文达到 2 时,由于 tcp_orphan_retries 为 2, 达到了最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK 报文),那么服务端就会断开连接。
  • 客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。

完!

怎么样,这下很清晰了吧

9.2 常见问题

9.2.1 TCP为什么是三次握手

TCP的三次握手是为了建立可靠的连接。它的主要目的是确保双方都能够发送和接收数据,同时解决网络中可能存在的问题。

首先,第一次握手是客户端向服务器发送连接请求报文段。这样做的原因是,客户端需要确保服务器可以正常工作,并且可以接受来自客户端的连接请求。

然后,第二次握手是服务器向客户端发送确认消息,表示接收到了客户端的连接请求。

最后,第三次握手是客户端向服务器发送确认消息,表示客户端已经收到了服务器的确认消息。这样做的目的是确保服务器能够正常接收到客户端的确认消息,以建立可靠的连接。

通过这三次握手,双方都可以确认对方可以正常工作,并且可以进行数据传输。此外,三次握手还可以解决网络中可能存在的重复请求、延迟、丢失等问题,确保数据的可靠传输。

9.2.1 TCP为什么是四次挥手

TCP为什么不设计成三次挥手

TCP的四次挥手过程是为了确保数据的完整性和可靠性。设计成三次挥手可能会导致一些问题。

首先,三次挥手无法处理最后一个数据包丢失的情况。在四次挥手中,主动关闭方发送FIN给被动关闭方,被动关闭方发送ACK给主动关闭方,然后被动关闭方发送FIN给主动关闭方,主动关闭方再发送ACK给被动关闭方。这样,即使在最后一个FIN的传输过程中发生数据包丢失,被动关闭方仍然可以重新发送FIN,以确保连接的关闭。但如果只有三次挥手,被动关闭方无法知道主动关闭方是否已经接收到了FIN,从而无法可靠地关闭连接

其次,四次挥手可以处理全双工连接的情况。TCP连接是全双工的,即数据可以双向传输。在四次挥手中,被动关闭方发送FIN给主动关闭方后,仍然可以接收主动关闭方发送的数据。如果只有三次挥手,被动关闭方发送FIN后就无法再接收数据了,这可能导致数据的丢失或不完整。

最后,四次挥手可以处理连接的半关闭。在某些情况下,一方可能希望先关闭发送方的数据流,但仍然可以接收对方发送的数据。在四次挥手中,被动关闭方发送FIN后仍然可以接收主动关闭方发送的数据。而如果只有三次挥手,被动关闭方发送FIN后就无法再接收数据了。

综上所述,TCP设计为四次挥手是为了确保数据的完整性和可靠性,处理丢包和全双工连接,并支持连接的半关闭。

在这里插入图片描述

TCP为什么不能三次挥手 被动关闭方FIN和ACK一起发

TCP不能使用三次挥手的原因是为了确保数据的可靠传输和连接的可靠关闭。在三次挥手的情况下,被动关闭方在发送ACK之前就发送FIN,这意味着被动关闭方在发送ACK之前已经关闭了连接。这可能导致主动关闭方未能正确接收到被动关闭方发送的ACK,从而无法确认连接已经关闭。

通过四次挥手过程,主动关闭方首先发送FIN给被动关闭方,被动关闭方收到FIN后发送ACK给主动关闭方,表示已经收到了关闭请求。然后,被动关闭方发送FIN给主动关闭方,主动关闭方收到FIN后发送ACK给被动关闭方,表示已经确认关闭请求。这样,双方都能正确地关闭连接,并且可以处理可能存在的延迟数据包,确保数据的可靠传输和连接的可靠关闭。

因为TCP是全双工通信的

(1)第一次挥手 因此当主动方发送断开连接的请求(即FIN报文)给被动方时,仅仅代表主动方不会再发送数据报文了,但主动方仍可以接收数据报文。

(2)第二次挥手 被动方此时有可能还有相应的数据报文需要发送,因此需要先发送ACK报文,告知主动方“我知道你想断开连接的请求了”。这样主动方便不会因为没有收到应答而继续发送断开连接的请求(即FIN报文)。

(3)第三次挥手 被动方在处理完数据报文后,便发送给主动方FIN报文;这样可以保证数据通信正常可靠地完成。发送完FIN报文后,被动方进入LAST_ACK阶段(超时等待)。

(4)第四挥手 如果主动方及时发送ACK报文进行连接中断的确认,这时被动方就直接释放连接,进入可用状态。

9.2.2 TIME_WAIT状态(2MSL)的作用

TIME_WAIT状态存在的理由:

  • 1)可靠地实现TCP全双工连接的终止
  • 2)允许老的重复分节在网络中消逝(避免新连接造成数据冲突)

在这里插入图片描述

9.2.3 为什么TCP网络编程中close前shutdown?

9.2.4 TCP 假状态/假连接

  • 上面可以看到TCP三次握手或四次挥手过程中,存在异常导致双方状态不能同步的情况,如何修复?
  • TCP 每一步分别对应一种状态显示(双方信息同步、不同步时异常如何处理修复?)
  • TCP 数据一直重传是什么原因,怎么修复?
  • 如何判断是否可以发送/接收数据(发送超时、keepalive)如何判断TCP连接是正常的

TCP 数据一直重传的原因可能有很多,以下是一些可能的原因和修复方法:

  1. 网络拥塞:网络拥塞可能导致数据包丢失或延迟,从而导致TCP数据重传。修复方法包括使用拥塞控制算法,如TCP拥塞控制算法中的拥塞避免、拥塞恢复机制等。

  2. 丢失的ACK:当接收方未及时发送确认应答(ACK)时,发送方可能会认为数据包丢失,从而触发数据重传。修复方法包括优化网络环境,尽量减少ACK的延迟,或者使用更可靠的传输协议。

  3. 网络错误或故障:网络错误或故障可能导致数据包丢失,从而触发数据重传。修复方法包括检查网络设备和连接,确保其正常工作,或者更换可靠的网络设备。

  4. 应用程序问题:应用程序可能存在bug或者处理速度较慢,导致数据包丢失或延迟,从而触发数据重传。修复方法包括检查应用程序代码,修复bug,并优化应用程序的性能。

如果你遇到TCP数据一直重传的问题,可以尝试以下步骤进行修复:

  1. 检查网络连接是否正常,确保没有网络故障或错误。

  2. 检查应用程序的代码,尤其是与网络通信相关的部分,修复可能存在的bug。

  3. 使用网络诊断工具,如Wireshark等,对网络数据包进行抓取和分析,查看是否有数据包丢失或延迟的情况。

  4. 根据分析结果,采取相应的措施修复问题,如优化网络环境、调整拥塞控制算法、修改应用程序逻辑等。

如果以上方法无法解决问题,建议寻求专业的网络工程师或系统管理员的帮助,他们可以根据具体情况提供更准确的解决方案。

9.2.5 如何判断TCP连接是否正常/可用?TCP连接的状态详解以及故障排查

9.2.5 TCP 网络阻塞数据一直重传会怎样?TCP-IP详解:重传机制

9.2.6 tcp 超时参数

原文地址:https://blog.csdn.net/zgb40302/article/details/114976728

建立连接过程中的超时

  • tcp_syn_retries :对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。
  • tcp_synack_retries :对于远端的连接请求SYN,内核会发送SYN + ACK数据报,以确认收到上一个 SYN连接请求包。这是所谓的三次握手( threeway handshake)机制的第二个步骤。这里决定内核在放弃连接之前所送出的 SYN+ACK 数目。

数据传输过程中的超时:

  • tcp_retries1:一旦重传超过阈值tcp_retries1,主要的动作就是更新路由缓存。用以避免由于路由选路变化带来的问题。

  • tcp_retries2:会直接放弃重传,关闭TCP流。另外:真正起到限制重传次数的并不是真正的重传次数。而是以tcp_retries1或tcp_retries2为boundary,以rto_base(如TCP_RTO_MIN 200ms)为初始RTO,计算得到一个timeout值出来。如果重传间隔超过这个timeout,则认为超过了阈值。

在这里插入图片描述

已经建立的连接,没有数据传输的超时:

  • tcp_keepalive_time
  • tcp_keepalive_probes
  • tcp_keepalive_intvl

9.2.7 内核关闭socket,应用再调用close会不会释放其他连接

9.2.8 TCP 报文需不需要帧头

消息头:长度 + 数据 + 校验(准确性) ,用来对字节流做切割。
在这里插入图片描述