TCP 四次挥手

四次挥手的过程

TCP 三次握手,一定是客户端先发起连接;而 TCP 四次挥手,发起中断连接的既可以是客户端,也可以是服务器。

本文以客户端先发起请求为例,刚开始双方都处于 ESTABLISHED 状态。

  1. 从报文的角度上看,四次挥手的过程如下:
  • 第一次挥手:客户端发送一个 FIN 报文(FIN=1,seq=u),进入 FIN_WAIT1 状态,等待服务端的确认,并停止发送数据

  • 第二次挥手:服务端回复 ACK 报文(ACK=1,ack=u+1,seq=v),表明已经收到客户端的报文了,进入 CLOSE_WAIT 状态

    客户端收到服务端的确认后,进入 FIN_WAIT2 状态,等待服务端的 FIN 报文。

  • 第三次挥手:如果服务端数据发送完毕想断开连接了,发送 FIN 报文(FIN=1,ACK=1,seq=w,ack=u+1)。进入 LAST_ACK 的状态,等待客户端的确认。

  • 第四次挥手:客户端回复 ACK 报文(ACK=1,seq=u+1,ack=w+1),进入 TIME_WAIT 状态,等待 2MSL 后进入 CLOSED 状态;服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状

  1. 从发送和接受数据的能力上看,四次挥手的过程如下:
  • 第一次挥手:客户端不再发送数据;服务器知道客户端不再发送数据

  • 第二次挥手:服务器对客户端 FIN 的确认,依然可能继续发送数据;客户端收到后会继续接收数据

  • 第三次挥手:服务器数据发送完毕后发起第三次挥手,表示服务器不再发送数据;客户端收到后会等 2MSL 后关闭连接,不再接收数据

  • 第四次挥手:服务器收到后关闭连接

什么是 MSL,为什么需要等 2MSL

MSL(Maximum Segment Lifetime),即报文段最大生存时间。可以简单理解为,报文段在网络内存在的最长时间,如果超过这个时间,报文段就会被丢弃。

根据第三版《UNIX 网络编程 卷 1》2.7 节,TIME_WAIT 状态的主要目的有两个:

  • 优雅的关闭 TCP 连接,也就是尽量保证被动关闭的一端收到它自己发出去的 FIN 报文的 ACK 确认报文;
  • 处理延迟的重复报文,这主要是为了避免前后两个使用相同四元组的连接中的前一个连接的报文干扰后一个连接。

注:很多博文只讨论了第一点,并且轻易的给出了错误的理由:最坏情况,ACK 报文的发送和 FIN 报文的重传各自需要 1MSL,因此是 2MSL。仔细想想之后,这些人的文章简直就是不经脑子的生搬硬套

假设 A 是主动关闭的一方,B 是被动关闭。

假如现在 A 收到 FIN 之后,为了实现目标 1,即保证 B 能够收到自己的 ACK 报文。那么 A 完美的等待时间不是 2MSL,而应该是从 B 发送第一个 FIN 报文开始计时到它最后一次重传 FIN 报文这段时长加上 MSL。但这个计算方式过于保守,只有在所有的 ACK 报文都丢失的情况下才需要这么长的时间;另外,第一个目标虽然重要,但并不十分关键,因为既然已经到了关闭连接的最后一步,说明在这个 TCP 连接上的所有用户数据已经完成可靠传输,所以要不要完美的关闭这个连接其实已经不是那么关键了。因此,(我猜)RFC 标准的制定者才决定以网络丢包不太严重为前提条件,然后根据第二个目标来计算 TIME_WAIT 状态应该持续的时长。

等待 2MSL 的真正目的是为了避免前后两个使用相同四元组的连接中的前一个连接的报文干扰后一个连接,换句话说,就是为了让此次 TCP 连接中的所有报文在网络中消失。

假如现在 A 发送 ACK 后,最坏情况下,这个 ACK 在 1MSL 时到达 B;此时 B 在收到这个 ACK 的前一刹那,一直在重传 FIN,这个 FIN 最坏会在 1MSL 时间内消失。因此从 A 发送 ACK 的那一刹那开始,等待 2MSL 可以保证 A 发送的最后一个 ACK,和 B 发送的最后一个 FIN 都在网络中消失

注:B 超时重传的时间并不是 2MSL;2MSL 时间为 240 秒,这是协议标准,一般超时重传只有 0.5 秒、1 秒、2 秒、4 秒……小于 MSL

为什么需要四次挥手

类比 TCP 的三次握手,这个问题可以理解为为什么服务器的 ACK 报文和 FIN 报文不能合并在一起发送?

原因是第一次挥手仅仅代表着客户端不再发送数据,而服务器收到 ACK 之后,可能还有数据想要发送给客户端,所以等待服务器发送完毕数据之后,才会继续发送 FIN 报文,因此无法合并发送

参考资料

  1. 四次挥手的过程
  2. 什么是 MSL,为什么需要等 2MSL
  3. 为什么需要四次挥手
  4. 参考资料