三次握手

  • 第一次握手:SYN(最开始都是 CLOSE,之后服务器进入 LISTEN)
    1. 发起连接:客户端发送一个 TCP 报文段到服务器。这个报文段的头部中,SYN 位被设置为 1,表明这是一个连接请求。同时,客户端会随机选择一个序列号(Sequence Number),假设为 x,发送给服务器。
    2. 目的:客户端通知服务器它希望建立连接,并告知服务器自己的初始序列号。
    3. 状态:客户端进入 SYN_SENT 状态。
  • 第二次握手:SYN + ACK
    1. 确认并应答:服务器收到客户端的连接请求后,如果同意建立连接,它会发送一个应答 TCP 报文段给客户端。在这个报文段中,SYN 位和 ACK 位都被设置为 1。服务器也会选择自己的一个随机序列号,假设为 y,并将客户端的序列号加 1(即 x+1)作为确认号(Acknowledgment Number),发送给客户端。
    2. 目的:服务器告诉客户端,它的连接请求被接受了,并通知客户端自己的初始序列号。
    3. 状态:服务器进入 SYN_RCVD 状态。
  • 第三次握手:ACK
    1. 最终确认:客户端收到服务器的应答后,还需要向服务器发送一个确认。这个 TCP 报文段的 ACK 位被设置为 1,确认号被设置为服务器序列号加 1(即 y+1),而自己的序列号是 x+1。
    2. 目的:客户端确认收到了服务器的同步应答,完成三次握手,建立连接。
    3. 状态:客户端进入 ESTABLISHED 状态,当服务器接收到这个包时,也进入 ESTABLISHED 状态
      三次握手

为什么 TCP 握手不能是两次?

  1. 防止服务器一直等待。
  2. 防止客户端已经失效的连接请求突然又传送到了服务器。
  • 例:客户端发起了 SYN=1 的第一次握手。服务器也及时回复了 SYN=2 和 ACK=1 的第二次握手,但是这个 ACK=1 的确认报文段因为某些原因在传输过程中丢失了,如果没有第三次握手告诉服务器,客户端收到了服务器的回应,那服务器是不知道客户端有没有接收到的。

二次握手

  • 还有一种情况是,一个旧的、延迟的连接请求(SYN=1)被服务器接受,导致服务器错误地开启一个不再需要的连接。

响应失败请求

  • 所以我们需要“三次握手”来确认这个过程:
    1. 第一次握手:客户端发送 SYN 包(连接请求)给服务器,如果这个包延迟了,客户端不会一直等待,它可能会重试并发送一个新的连接请求。
    2. 第二次握手:服务器收到 SYN 包后,发送一个 SYN-ACK 包(确认接收到连接请求)回客户端。
    3. 第三次握手:客户端收到 SYN-ACK 包后,再发送一个 ACK 包给服务器,确认收到了服务器的响应。

第三次握手可以携带数据吗?

  • 第 3 次握手是可以携带数据的。
  • 此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,它已经建立连接成功,并且确认服务端的接收和发送能力是正常的。
  • 第一次握手不能携带数据是出于安全的考虑,因为如果允许携带数据,攻击者每次在 SYN 报文中携带大量数据,就会导致服务端消耗更多的时间和空间去处理这些报文,会造成 CPU 和内存的消耗。

四次挥手

  • 第一次挥手(FIN):客户端向服务器发送一个 FIN(结束)报文,表示客户端没有数据要发送了,但仍然可以接收数据。客户端进入 FIN-WAIT-1 状态。
  • 第二次挥手(ACK):服务器接收到 FIN 报文后,向客户端发送一个 ACK 报文,确认已接收到客户端的 FIN 请求。服务器进入 CLOSE-WAIT 状态,客户端进入 FIN-WAIT-2 状态。
  • 第三次挥手(FIN):服务器向客户端发送一个 FIN 报文,表示服务器也没有数据要发送了。服务器进入 LAST-ACK 状态。
  • 第四次挥手(ACK):客户端接收到 FIN 报文后,向服务器发送一个 ACK 报文,确认已接收到服务器的 FIN 请求。客户端进入 TIME-WAIT 状态,等待一段时间以确保服务器接收到 ACK 报文。服务器接收到 ACK 报文后进入 CLOSED 状态。客户端在等待一段时间后也进入 CLOSED 状态。
    四次挥手

TCP 挥手为什么需要四次呢?

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
  • 从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。

TCP 四次挥手过程中,为什么需要等待 2MSL, 才进入 CLOSED 关闭状态?

为什么需要等待?

  1. 为了保证客户端发送的最后一个 ACK 报文段能够到达服务端。 这个 ACK 报文段有可能丢失,因而使处在 LAST-ACK 状态的服务端就收不到对已发送的 FIN + ACK 报文段的确认。服务端会超时重传这个 FIN+ACK 报文段,而客户端就能在 2MSL 时间内(超时 + 1MSL 传输)收到这个重传的 FIN+ACK 报文段。接着客户端重传一次确认,重新启动 2MSL 计时器。最后,客户端和服务器都正常进入到 CLOSED 状态。
  2. 防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接请求报文段。

为什么等待的时间是 2MSL?

  • MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃
  • TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
  • 例:被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

TCP 三次握手和四次挥手,中间失败了会发生什么?

TCP 三次握手丢包情况

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

  • 当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。
  • 在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的
  • 不同版本的操作系统可能超时时间不同,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,如果想要更改则需要重新编译内核,比较麻烦。
  • 当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会重发 SYN 报文,那到底重发几次呢?
  • 在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries 内核参数控制,这个参数是可以自定义的,默认值一般是 5。
  • 通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。每次超时的时间是上一次的 2 倍
  • 当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。
  • 所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。

第一次握手丢包过程图解

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

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

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

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

  • 然后,因为第二次握手中包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。
  • 那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文
  • 在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定,默认值是 5。
  • 因此,当第二次握手丢失了,客户端和服务端都会重传
    1. 客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由 tcp_syn_retries 内核参数决定;
    2. 服务端会重传 SYN-ACK 报文,也就是第二次握手,最大重传次数由 tcp_synack_retries 内核参数决定。
  • 举个例子
    • 假设 tcp_syn_retries 参数值为 1,tcp_synack_retries 参数值为 2,那么当第二次握手一直丢失时,发生的具体过程如下:
      1. 当客户端超时重传 1 次 SYN 报文后,由于 tcp_syn_retries 为 1,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到服务端的第二次握手(SYN-ACK 报文),那么客户端就会断开连接。
      2. 当服务端超时重传 2 次 SYN-ACK 报文后,由于 tcp_synack_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。

第二次握手丢包过程图解

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

  • 客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。
  • 因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数
  • 注意:ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文
  • 举个例子
    • 假设 tcp_synack_retries 参数值为 2,那么当第三次握手一直丢失时,发生的过程如下:
      1. 当服务端超时重传 2 次 SYN-ACK 报文后,由于 tcp_synack_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍)
      2. 如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。

第三次握手丢包过程图解

TCP 四次挥手丢包情况

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

  • 当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。
  • 正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2 状态。
  • 如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。
  • 当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。
  • 举个例子
    • 假设 tcp_orphan_retries 参数值为 3,当第一次挥手一直丢失时,发生的过程如下:
      1. 当客户端超时重传 3 次 FIN 报文后,由于 tcp_orphan_retries 为 3,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍)
      2. 如果还是没能收到服务端的第二次挥手(ACK报文),那么客户端就会断开连接。

第一次挥手丢包过程图解

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

  • 当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。
  • 在前面我们也提了,ACK 报文是不会重传的,所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
  • 举个例子:
    • 假设 tcp_orphan_retries 参数值为 2,当第二次挥手一直丢失时,发生的过程如下:
      1. 当客户端超时重传 2 次 FIN 报文后,由于 tcp_orphan_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍)
      2. 如果还是没能收到服务端的第二次挥手(ACK 报文),那么客户端就会断开连接。

第二次挥手丢包过程图解

  • 这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 FIN 报文。
  • 对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。
  • 这意味着对于调用 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,当第三次挥手一直丢失时,发生的过程如下:
      1. 当服务端重传第三次挥手报文的次数达到了 3 次后,由于 tcp_orphan_retries 为 3,达到了重传最大次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。
      2. 客户端因为是通过 close 函数关闭连接的,处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。

第三次挥手丢包过程图解

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

  • 当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。
  • 在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。
  • 然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。
  • 如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。
  • 举个例子
    • 假设 tcp_orphan_retries 为 2,当第四次挥手一直丢失时,发生的过程如下:
      1. 当服务端重传第三次挥手报文达到 2 时,由于 tcp_orphan_retries 为 2, 达到了最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第四次挥手(ACK 报文),那么服务端就会断开连接。
      2. 客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。

第四次挥手丢包过程图解