TCP 状态机:一条连接的 11 种状态
更新时间:2026-09-17。本文是「网络协议」域传输层主题,讲 TCP 连接从建立到释放经过的状态。偏性能调优的部分(TIME_WAIT 调参、连接表)见 TIME_WAIT 原理,握手/挥手报文细节见同目录配套篇。
TCP 是有状态的可靠传输协议:两端各自维护一个连接状态,随收到/发出的控制位(SYN/ACK/FIN/RST)迁移。看懂状态机,就能解释 ss -tan 里每一列的含义、以及两类最常见的异常堆积。
一、建连阶段的状态
| 状态 | 在哪一端 | 含义 |
|---|---|---|
CLOSED | 两端 | 起点/终点,没有连接 |
LISTEN | 服务端 | 已 bind/listen,等待 SYN |
SYN_SENT | 客户端 | 已发 SYN,等 SYN+ACK |
SYN_RCVD | 服务端 | 收到 SYN 并回了 SYN+ACK,等 ACK |
ESTABLISHED | 两端 | 三次握手完成,可双向传数据 |
客户端 服务端
CLOSED LISTEN
|--- SYN ----------------------->| 服务端 -> SYN_RCVD
|SYN_SENT |
|<-- SYN+ACK --------------------|
|--- ACK ----------------------->|
ESTABLISHED ESTABLISHED建连用 3 个报文(SYN、SYN+ACK、ACK),原因是要双向各确认一次序列号能力并交换初始序列号 ISN;两次无法防止历史的失效连接被误建立。详见三次握手篇。
二、关闭阶段:主动方与被动方走不同的路
关闭是四次挥手,主动关闭方与被动关闭方经历的状态完全不同,这是理解状态机的关键。
主动关闭方(先调 close 的一端):
| 状态 | 触发 |
|---|---|
FIN_WAIT_1 | 发出 FIN |
FIN_WAIT_2 | 收到对 FIN 的 ACK(对方还没发 FIN) |
CLOSING | 发了 FIN 却先收到对方的 FIN(几乎同时关闭,ACK 丢了时短暂出现) |
TIME_WAIT | 收到对方 FIN 并回 ACK 后,等待 2MSL |
CLOSED | 2MSL 后关闭 |
被动关闭方:
| 状态 | 触发 |
|---|---|
CLOSE_WAIT | 收到对方 FIN、回了 ACK,但本端应用还没 close |
LAST_ACK | 本端应用终于 close、发出 FIN,等最后一个 ACK |
CLOSED | 收到 ACK |
主动方 被动方
ESTABLISHED ESTABLISHED
|--- FIN ----------------------->| -> CLOSE_WAIT
FIN_WAIT_1 |(应用读 EOF,处理后 close)
|<-- ACK ------------------------|
FIN_WAIT_2 |
|<-- FIN ------------------------| -> LAST_ACK
|--- ACK ----------------------->|
TIME_WAIT CLOSED
(2MSL) -> CLOSED三、TIME_WAIT:为什么主动方要等 2MSL
主动关闭方发完最后一个 ACK 后进入 TIME_WAIT,停 2MSL(Maximum Segment Lifetime,报文最大生存时间的两倍,Linux 默认约 60s),两个目的:
- 让最后的 ACK 丢失时能重发:若 ACK 丢了,被动方会重传 FIN,主动方还在 TIME_WAIT 就能再回一个 ACK;若已 CLOSED 就只能回 RST。
- 让本次连接的旧报文在网络中自然消亡,避免相同四元组的新连接收到上一条连接的延迟数据。
TIME_WAIT 多不占文件描述符、也不是"连接泄漏",高并发短连接的主动关闭方出现成千上万属正常。只有在它耗尽本地临时端口或 conntrack 表时才需调
tcp_tw_reuse、扩端口范围,详见 TIME_WAIT 性能篇。注意 TIME_WAIT 总在主动关闭方——想让它出现在客户端,就让服务端先关连接。
四、CLOSE_WAIT 堆积:这才是真 bug
如果 ss -tan | grep CLOSE_WAIT 很多且长期不消,说明对端已经发了 FIN(想关),本端内核也回了 ACK,但应用层迟迟没有调用 close(),连接卡在 CLOSE_WAIT。
- 根因几乎都是应用代码:读到 EOF/异常后没有正确关闭 socket、连接泄漏、或线程池满了没人处理关闭。
- 它会占 fd,堆积多了触发
too many open files。 - 处理方向是修应用(确保 finally/异常路径关闭连接),而不是调内核参数。对比 TIME_WAIT 是协议正常态、CLOSE_WAIT 是程序缺陷态。
五、用 ss 观察真实状态
ss -tan # 所有 TCP 连接及状态
ss -ant | awk 'NR>1{c[$1]++}END{for(s in c)print c[s],s}' | sort -rn
ss -tan state time-wait | wc -l # TIME_WAIT 数量
ss -tan state close-wait | wc -l # CLOSE_WAIT 数量(应接近 0)
ss -tan state established
ss -ti '( dport = :443 )' # 含 RTT、拥塞窗口、重传等详细信息RST(复位)是状态机的"异常直达 CLOSED"通道:收到 RST 立即释放连接不经过四次挥手,常见于端口没监听、连接已不存在仍收到数据、或设置了 SO_LINGER 强制 RST 关闭。
相关阅读
握手报文与 ISN 见三次握手,挥手细节见四次挥手,TIME_WAIT 调优见 concepts/network/tcp-timewait,连接计数见网络连接数 FAQ。
总结
TCP 11 个状态围绕"建连(LISTEN→SYN→ESTABLISHED)"和"挥手(主动方 FIN_WAIT→TIME_WAIT、被动方 CLOSE_WAIT→LAST_ACK)"两条路径;TIME_WAIT 在主动关闭方停留 2MSL 是协议正常态、多但无害,CLOSE_WAIT 长期堆积则是应用没 close 套接字的 bug,用 ss -tan 按状态统计即可区分。