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 状态的套接字,占用文件描述符和端口资源。