TCP四次挥手

建立连接需要“三次握手”,而断开连接之所以需要**“四次挥手”,根本原因在于 TCP 协议是全双工通信**(也就是双方可以同时、独立地收发数据)。

#tech / network #type / concept #status / seed

TCP 四次挥手详解

一、四次挥手流程(以客户端主动关闭为例)

步骤发送方报文类型状态变化关键说明
第一次挥手客户端FIN (Seq=u)ESTABLISHEDFIN_WAIT_1客户端不再发送数据,但可接收
第二次挥手服务端ACK (Ack=u+1)CLOSE_WAIT服务端可能仍有数据待发送
第三次挥手服务端FIN (Seq=w)CLOSE_WAITLAST_ACK服务端数据发送完毕
第四次挥手客户端ACK (Ack=w+1)FIN_WAIT_2TIME_WAIT客户端等待 2MSL 后关闭连接

1. 第一次挥手(客户端 -> 服务端)

  • 动作:客户端的数据发送完毕了,打算断开连接。它会向服务端发送一个带有 FIN=1 标志位的数据包,并带上自己的序列号 seq = u。发送后,客户端进入 FIN_WAIT_1 状态。
  • 潜台词“服务端你好,我这边的数据全部发完了,我要关闭发送通道了。”
  • 注意:此时客户端只是不能数据了,但仍然可以数据(这叫半关闭状态)。

2. 第二次挥手(服务端 -> 客户端)

  • 动作:服务端收到了客户端的 FIN 包,立刻回复一个带有 ACK=1 标志位的确认包,确认号 ack = u + 1,序列号 seq = v。服务端进入 CLOSE_WAIT 状态;客户端收到后进入 FIN_WAIT_2 状态。
  • 潜台词“收到!但我可能还有些剩余的数据没传完,你先等我一下,我发完了再告诉你。”
  • 注意:为什么这里不直接连着服务端的 FIN 一起发(变成三次挥手)?就是因为服务端此时可能还有业务数据在缓冲区没有发送完毕,必须先把剩下的数据传完。

3. 第三次挥手(服务端 -> 客户端)

  • 动作:等服务端把剩下的数据也全部发送完毕后,它也打算关闭发送通道了。于是向客户端发送一个带有 FIN=1ACK=1 标志位的数据包,序列号 seq = w。发送后,服务端进入 LAST_ACK(最后确认)状态。
  • 潜台词“好了,我这边的数据也彻底发完了,我的发送通道也可以关闭了。”

4. 第四次挥手(客户端 -> 服务端)

  • 动作:客户端收到了服务端的 FIN 包,必须给服务端一个交代,于是回复一个带有 ACK=1 标志位的确认包,确认号 ack = w + 1。服务端收到这个确认包后,立刻进入 CLOSED 状态,彻底关闭连接。
  • 潜台词“收到!那你关吧,我也准备彻底关了。”
  • 高频考点(TIME_WAIT):客户端发送完第四次挥手的 ACK 后,并不会立刻关闭,而是会进入一个极其特殊的 TIME_WAIT 状态,必须等待 2MSL(最大报文段生存时间,约等于 1 到 4 分钟)后,才会真正进入 CLOSED 状态。

注意

  • 主动关闭方会进入 TIME_WAIT 状态,被动关闭方直接进入 CLOSED 状态。
  • 实际抓包中第一次挥手可能是 FIN+ACK(因 TCP 头部 ACK 字段默认有效)。

二、为什么需要四次挥手?

  1. 全双工特性:TCP 连接双向独立关闭,服务端需等待数据发送完毕后再发送 FIN
  2. 可靠性保证:若合并第二次和第三次挥手(ACK + FIN),可能导致客户端误判 FIN 丢失而重复重传。

三、关键状态解析

1. TIME_WAIT(2MSL 等待)

  • 作用
    • 确保最后一个 ACK 到达服务端(若丢失,服务端会重传 FIN)。
    • 消除网络中残留的旧报文,避免干扰新连接。
  • 默认时长:Linux 中为 60 秒(2×MSL),可通过 tcp_fin_timeout 调整。

2. CLOSE_WAIT

  • 触发条件:被动关闭方收到 FIN 但未调用 close()
  • 风险:若程序未及时关闭连接,会导致连接长期滞留(理论无限长)。

四、异常处理

挥手丢失步骤处理机制重传控制参数
第一次挥手客户端重传 FIN,超时后直接关闭tcp_orphan_retries
第二次挥手客户端重传 FIN(服务端 ACK 不重传)tcp_orphan_retries
第三次挥手服务端重传 FIN,客户端在 FIN_WAIT_2 状态超时(默认 60 秒)后关闭tcp_fin_timeout
第四次挥手服务端重传 FIN,客户端在 TIME_WAIT 状态持续 2MSL 后关闭-

:若程序崩溃,系统会强制发送 RST 终止连接。

五、优化建议

  1. 减少 TIME_WAIT
    • 启用 net.ipv4.tcp_tw_reuse(需时间戳支持)。
    • 避免短连接高并发场景(如改用长连接)。
  2. 排查 CLOSE_WAIT:检查代码是否漏调 close() 或存在阻塞。

创建于 2025/1/1 更新于 2026/5/27