TCP 连接状态机是怎么流转的?各状态有什么作用?
简化版
TCP 状态机描述一条连接从建立、传输到关闭的完整生命周期。面试要抓住三条主线:主动打开走 SYN_SENT,被动打开走 LISTEN/SYN_RECEIVED,主动关闭走 FIN_WAIT_1/FIN_WAIT_2/TIME_WAIT,被动关闭走 CLOSE_WAIT/LAST_ACK。
详细版
TCP 常见状态可以按阶段记:
- 建连阶段:服务端
LISTEN,客户端发 SYN 后进入SYN_SENT,服务端收到 SYN 后进入SYN_RECEIVED,三次握手完成后双方进入ESTABLISHED。 - 传输阶段:
ESTABLISHED表示连接可双向收发数据。 - 主动关闭:主动发 FIN 的一方进入
FIN_WAIT_1,收到对方 ACK 后进入FIN_WAIT_2,再收到对方 FIN 并回 ACK 后进入TIME_WAIT,等待 2MSL 后CLOSED。 - 被动关闭:收到 FIN 的一方先回 ACK 并进入
CLOSE_WAIT,等应用调用 close 后发 FIN 进入LAST_ACK,收到最终 ACK 后CLOSED。 - 异常关闭:收到或发送 RST 通常直接释放连接,不走完整 FIN 流程。
面试追问通常会落到 TIME_WAIT 和 CLOSE_WAIT:大量 TIME_WAIT 常见于主动关闭连接过多,大量 CLOSE_WAIT 多半是应用没有及时关闭 socket。
完整版教学
一、为什么 TCP 需要这么多状态
TCP 是面向连接的字节流协议,它要在不可靠网络上维护“双方都认为连接处于同一阶段”的共同认知。网络会丢包、乱序、延迟,所以 TCP 不能只用“已连接/未连接”两个状态,否则很难处理半关闭、重复 FIN、迟到报文和异常复位。
可以把 TCP 状态机理解成连接生命周期的账本:每发出一个控制报文,就记录自己下一步在等什么;每收到一个控制报文,就根据当前状态判断是否推进、重传、忽略或复位。
状态 = 当前连接阶段 + 正在等待的对端动作
例如:
FIN_WAIT_1 = 我已经发 FIN,正在等对端 ACK 或 FIN
CLOSE_WAIT = 我收到对端 FIN,已经 ACK,正在等本地应用 close
TIME_WAIT = 我已回最后 ACK,正在等旧报文自然消失
记忆钩子:TCP 状态名不是死记硬背,它们大多在说“我刚做了什么,以及我正在等什么”。
二、建连状态:LISTEN、SYN_SENT、SYN_RECEIVED、ESTABLISHED
服务端先调用 listen,进入 LISTEN,它不是“已经连接”,而是准备接收 SYN。客户端主动发 SYN 后进入 SYN_SENT,意思是“我发起连接了,正在等 SYN+ACK”。服务端收到 SYN 后进入 SYN_RECEIVED,回 SYN+ACK,等待客户端最后的 ACK。
三次握手完成后,双方才进入 ESTABLISHED。这个状态很重要,因为只有它表示连接已经可以稳定双向传输应用数据。
Client Server
CLOSED LISTEN
| -- SYN --------------------> |
SYN_SENT SYN_RECEIVED
| <------- SYN + ACK --------- |
| -- ACK --------------------> |
ESTABLISHED ESTABLISHED
举个数字例子:客户端初始序列号 seq=1000,服务端初始序列号 seq=8000。客户端第三次握手里的 ACK=8001 表示服务端的 SYN 已被确认,服务端收到后才认为连接建立完成。
三、正常关闭:主动关闭方为什么会经过 FIN_WAIT 和 TIME_WAIT
TCP 是全双工协议,两个方向的数据流可以独立关闭。主动关闭方发 FIN,只表示“我这边不再发送数据”,不等于对方也没有数据要发。因此主动关闭方收到 ACK 后进入 FIN_WAIT_2,继续等待对方的 FIN。
当主动关闭方收到对方 FIN 后,会回最后一个 ACK 并进入 TIME_WAIT。它不马上消失,是为了处理两个问题:第一,最后 ACK 可能丢失,需要能够重发;第二,网络中旧连接的迟到报文要等到过期,避免污染下一条相同四元组连接。
| 状态 | 谁更常见 | 含义 | 常见风险 |
|---|---|---|---|
FIN_WAIT_1 | 主动关闭方 | 已发 FIN,等 ACK/FIN | 对端无响应 |
FIN_WAIT_2 | 主动关闭方 | 对端 ACK 了我的 FIN,等对端 FIN | 对端迟迟不关闭 |
TIME_WAIT | 主动关闭方 | 已回最后 ACK,等待 2MSL | 短连接高并发下占端口 |
CLOSING | 双方同时关闭 | 双方几乎同时发 FIN | 较少见 |
四、被动关闭:CLOSE_WAIT 和 LAST_ACK 重点看应用层
被动关闭方收到 FIN 后,内核会先回 ACK,连接进入 CLOSE_WAIT。这个状态表示“对方不发了,但本地应用还没有调用 close”。所以大量 CLOSE_WAIT 往往不是网络问题,而是应用代码没有释放连接、业务线程卡住、连接池归还逻辑异常。
应用真正调用 close 后,被动关闭方才发 FIN,进入 LAST_ACK,等待主动关闭方的最后 ACK。收到 ACK 后连接关闭。
对端 FIN 到达
|
v
内核回 ACK -> CLOSE_WAIT
|
| 应用调用 close()
v
发送 FIN -> LAST_ACK -> 收到 ACK -> CLOSED
排查时可以按数字看:如果某服务长期保持 CLOSE_WAIT=3000,而请求量并没有相应峰值,优先查代码是否没有关闭响应体、socket、数据库代理连接或上游 HTTP 连接。
五、同时关闭和异常关闭怎么理解
如果双方几乎同时调用 close,双方都可能在收到对端 FIN 前先发出自己的 FIN,于是进入 CLOSING。这个状态不常见,但它说明 TCP 的关闭不是单向脚本,而是双方都可能独立行动。
异常关闭主要和 RST 有关。RST 表示连接被重置,通常不会走 FIN_WAIT、CLOSE_WAIT、TIME_WAIT 的优雅关闭流程。比如访问一个没有监听的端口、应用强制关闭连接、收到不符合当前状态的报文,都可能触发 RST。
正常关闭:FIN -> ACK -> FIN -> ACK 保留半关闭语义
异常关闭:RST 直接告诉对端连接不可继续
面试里要把 FIN 和 RST 区分开:FIN 是“我不再发送,但还能收”,RST 是“这条连接作废”。
六、用状态机排查线上问题
状态机不是背概念用的,它能直接指导排障。看到不同状态数量异常,要先判断是主动关闭方多,还是被动关闭方多;是网络迟滞,还是应用没有释放资源。
| 现象 | 更可能的原因 | 排查方向 |
|---|---|---|
TIME_WAIT 多 | 本机主动关闭短连接多 | 连接复用、端口范围、关闭策略 |
CLOSE_WAIT 多 | 应用收到 FIN 后未 close | 代码释放、线程阻塞、连接池 |
SYN_SENT 多 | 对端不可达或被拦截 | 路由、防火墙、目标端口 |
SYN_RECEIVED 多 | 握手未完成 | 半连接队列、SYN flood、回包路径 |
FIN_WAIT_2 多 | 对端不发 FIN | 对端应用关闭逻辑 |
一个常见判断公式是:
主动关闭问题优先看 TIME_WAIT / FIN_WAIT_*
被动关闭问题优先看 CLOSE_WAIT / LAST_ACK
建连问题优先看 SYN_SENT / SYN_RECEIVED
七、常见误区与追问
- 误区:ESTABLISHED 表示永远可用。 它只表示 TCP 状态已建立,应用层超时、对端崩溃、链路中断仍可能让读写失败。
- 误区:CLOSE_WAIT 是 TCP 协议缺陷。 多数情况下它暴露的是应用没有调用 close 或资源释放路径被阻塞。
- 误区:TIME_WAIT 一定是坏事。 它承担重发最后 ACK 和隔离旧报文的作用,数量是否异常要结合短连接规模判断。
- 追问:为什么主动关闭方进入 TIME_WAIT? 因为主动关闭方发出了最后 ACK,需要在 ACK 丢失时还能重发,并等待旧报文过期。
- 追问:SYN_RECEIVED 很多说明什么? 说明握手停在服务端收到 SYN 后,可能是半连接队列压力、攻击、回包丢失或客户端异常。
- 追问:FIN_WAIT_2 很多怎么查? 先确认本机是否主动关闭,再查对端为什么迟迟不发送 FIN,以及是否有超时兜底。
八、加强记忆
把 TCP 状态机串成四段:建连看 LISTEN -> SYN_SENT/SYN_RECEIVED -> ESTABLISHED;主动关闭看 FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT;被动关闭看 CLOSE_WAIT -> LAST_ACK;异常关闭看 RST。面试答题时先画生命周期,再解释 TIME_WAIT 和 CLOSE_WAIT 的线上含义,就能从背状态升级到会排障。