CLIENT
{{ clientState }}
2MSL TIMER
{{ pkt.text }}
SERVER
{{ serverState }}
[{{ log.time }}] {{ log.msg }}
知识点:优雅关闭与 2MSL 等待
Q: 如果不调用 shutdown 而直接 close(),是否会产生 TIME_WAIT 或 CLOSE_WAIT? A: 绝对会。 直接调用 `close()`(或进程意外退出)是触发这些状态最常见的方式
当一个进程关闭套接字时,操作系统内核会自动发送 `FIN` 包。
  • 如果你主动调用 `close()`,你的连接会进入 TIME_WAIT
  • 如果你是被动接收到对方的关闭信号,而你的代码没处理好,你就会卡在 CLOSE_WAIT
`shutdown()` 只是提供了更精细的“半关闭”控制,而 `close()` 则是“全关闭”,它们都会触发四次挥手流程。
Q: 如果主动关闭方收到了 shutdown 和 close,优雅的关闭连接,是否也需要“主动关闭方必须等待 2MSL”? A: 是的,必须等待。
只要是标准的 TCP 优雅关闭(即通过 FIN 包挥手,而非 RST 暴力重置),主动发起关闭的那一方(Active Closer),在发送完最后一个 ACK 后,必须进入 TIME_WAIT 状态并等待 2MSL。(谁先发起关闭(发送第一个 FIN),谁就必须承担 2MSL 的等待成本)
为什么“优雅”反而要“罚站”?
  • 确保对方收到最后的告别 (ACK):
    * 主动关闭方发出的最后一个 ACK 可能会丢失。
    * 如果丢失,被动关闭方(Server)会超时重发 FIN。
    * 如果主动关闭方(Client)不等待 2MSL 直接销毁连接(CLOSED),当它收到这个重传的 FIN 时,因为找不到连接记录,会回复一个 RST(报错)。这会导致对方认为连接是“异常断开”的,而不是“优雅关闭”。
  • 防止“前朝余孽”干扰“新朝代”:
    * TCP 是由 IP:Port 对定义的。如果你立刻关闭并释放端口,操作系统可能会立刻把这个端口分配给一个新的连接。
    * 如果网络中有个迷路的旧数据包(延迟很高)此时到达,它可能会被误认为是新连接的数据,导致数据错乱。
    * 2MSL 的时间足以保证网络中所有的旧数据包自然消亡。
  • 谁来承担这 2MSL?
    这是设计系统时的关键:谁先发起关闭,谁就得背负 2MSL 的资源消耗。
    * 场景 1 (Client 主动):Client 进入 TIME_WAIT,Server 不需要等。
    * 场景 2 (Server 主动):Server 进入 TIME_WAIT,Client 不需要等。
  • 可以在动画中验证
    你可以再次运行动画的 "1. 完整生命周期":
    1. 注意看 Server(被动方):它发完 FIN,收到 ACK 后,状态直接变为 CLOSED,没有进度条,不需要等待。
    2. 注意看 Client(主动方):它发完最后的 ACK 后,必须弹出黄色进度条等待。

    这正是 TCP 协议公平且严谨的地方:你想主动结束,你就得负责善后。
结论:这就是为什么高性能服务器(如 Nginx, Redis)通常尽量避免主动关闭连接,或者让客户端控制超时。因为如果服务器主动关闭成千上万个连接,服务器上就会堆积成千上万个 TIME_WAIT 状态的套接字,占用文件描述符和端口资源。