2026-06-09 / C# / .NET Core
TCP 连接生命周期:三次握手、四次挥手与 TIME_WAIT
从可靠双向连接的角度理解 TCP 三次握手、四次挥手、半关闭、TIME_WAIT,以及排查连接问题时应该看什么。
TCP 连接生命周期:三次握手、四次挥手与 TIME_WAIT
TCP 的目标不是“尽快发出去”,而是可靠地建立一条可用的双向字节流。所谓连接生命周期,本质上是在解决三件事:双方是否都能收发、序列号从哪里开始、连接如何有序关闭。
如果只记住 SYN、ACK、FIN,很容易把它当成背诵题。更好的理解方式是:TCP 一直在维护“两个方向的数据流”和“双方各自的状态机”。
建立连接前,双方要确认什么
客户端和服务端在正式传数据之前,至少要确认四件事:
- 客户端能发,服务端能收。
- 服务端能发,客户端能收。
- 双方的初始序列号已经交换。
- 双方都知道连接已经进入可用状态。
sequenceDiagram
participant C as Client
participant S as Server
C->>S: SYN, seq=x
S-->>C: SYN + ACK, seq=y, ack=x+1
C->>S: ACK, ack=y+1
三次握手每一步解决什么
第一次握手:客户端发送 SYN,告诉服务端“我想建立连接,我的初始序列号是 x”。这一步只能证明客户端能发到服务端。
第二次握手:服务端返回 SYN + ACK,告诉客户端“我收到了你的 SYN,我的初始序列号是 y”。这一步证明服务端能收,也能发回客户端。
第三次握手:客户端返回 ACK,告诉服务端“我也收到了你的 SYN”。到这里,服务端才知道自己的发送能力也被客户端确认了。
所以三次握手不是为了凑次数,而是为了完成双向收发能力和初始序列号的确认。
为什么不是两次握手
如果只有两次,服务端无法确认客户端是否真的收到了自己的 SYN + ACK。更麻烦的是,网络里可能存在延迟到达的旧 SYN。两次握手更容易让服务端误以为一个过期连接仍然有效。
第三次 ACK 的意义,是让服务端确认“客户端已经收到我的确认,并且愿意进入连接状态”。
数据传输时,ACK 在确认什么
TCP 是字节流协议,ACK 确认的是“下一个期望收到的字节序号”,不是某个业务消息。应用层的一次写入,不一定等于网络上的一个包;网络上的多个包,也可能被应用层一次读取到。
这也是为什么业务协议需要自己设计消息边界,例如长度前缀、分隔符或固定格式。
为什么断开通常是四次挥手
TCP 是全双工连接。客户端停止发送,不代表服务端也已经发送完。关闭连接时,两个方向需要分别关闭。
sequenceDiagram
participant A as 主动关闭方
participant B as 被动关闭方
A->>B: FIN
B-->>A: ACK
B-->>A: FIN
A->>B: ACK
第一步 FIN 表示“我这边没有数据要发了”。对方 ACK 后,只代表它确认了这个方向关闭。被动关闭方可能还有数据要发,所以它会等自己发送完,再发出自己的 FIN。
这就是四次挥手的核心:两个方向分别结束。
TIME_WAIT 为什么存在
主动关闭方发出最后一个 ACK 后,通常会进入 TIME_WAIT。它看起来像“连接已经结束但端口还被占着”,但它有两个重要作用:
- 确保最后一个 ACK 如果丢失,对方重发 FIN 时还能再次 ACK。
- 等待旧连接中的延迟报文在网络中自然消失,避免污染后续同四元组连接。
TIME_WAIT 不是异常,而是 TCP 为可靠关闭付出的成本。服务端如果出现大量 TIME_WAIT,要先看是谁主动关闭连接,以及连接复用、Keep-Alive、连接池配置是否合理。
和应用开发最相关的排查点
- 连接建立慢:关注 DNS、网络延迟、SYN 重传、服务端 backlog。
- 偶发连接失败:关注防火墙、端口耗尽、连接池上限、服务端 accept 能力。
- 大量 TIME_WAIT:关注主动关闭方、短连接比例、HTTP Keep-Alive。
- 大量 CLOSE_WAIT:通常说明应用收到了对方 FIN,但自己没有及时关闭 socket。
- 请求读写异常:区分 TCP 连接问题和应用层协议边界问题。
小结
三次握手解决的是连接建立前的双向确认和初始序列号同步。四次挥手解决的是全双工连接的双向关闭。TIME_WAIT 解决的是最后确认和旧报文消散问题。
理解这些状态,不是为了在面试里背流程,而是为了在服务通信、连接池、网关、gRPC、HTTP API 排查时能知道自己到底在看哪一层。