TCP 状态转移解析
TCP(传输控制协议)是一种面向连接、可靠的传输层协议,其状态转移机制是实现“三次握手”建立连接、“四次挥手”关闭连接及数据传输过程中异常处理的核心。理解 TCP 状态转移,需先明确 11 种核心状态,再围绕“连接建立-数据传输-连接关闭”三大阶段梳理状态变迁逻辑。
一、TCP 核心状态定义
首先明确每种状态的含义,这是理解状态转移的基础:
| 状态名称(英文) | 状态名称(中文) | 核心含义 |
| CLOSED | 关闭状态 | TCP连接的初始/最终状态,无任何连接活动。 |
| LISTEN | 监听状态 | 服务器端处于“等待客户端连接请求”的状态(如调用`listen()`后)。 |
| SYN_SENT | 同步已发送 | 客户端发送连接请求(SYN报文)后,等待服务器确认的状态。 |
| SYN_RCVD | 同步已接收 | 服务器收到客户端SYN报文,回复SYN+ACK报文后,等待客户端最终确认的状态。 |
| ESTABLISHED | 已建立连接 | 客户端与服务器完成“三次握手”,连接正式建立,可进行数据传输的状态(核心工作状态)。 |
| FIN_WAIT_1 | 终止等待1 | 主动关闭方(客户端/服务器均可)发送关闭请求(FIN报文)后,等待对方确认的状态。 |
| FIN_WAIT_2 | 终止等待2 | 主动关闭方收到对方对FIN的确认(ACK报文)后,等待对方发送其自身FIN报文的状态。 |
| CLOSE_WAIT | 关闭等待 | 被动关闭方收到主动关闭方的FIN报文并回复ACK后,等待自身应用层“确认关闭”的状态(此时仍可接收数据)。 |
| CLOSING | 关闭中 | 双方同时发起关闭请求:主动关闭方发送FIN后,未收到ACK却先收到对方的FIN,此时进入该状态,等待对方ACK。 |
| LAST_ACK | 最后确认 | 被动关闭方发送自身的FIN报文后,等待主动关闭方确认(ACK)的状态。 |
| TIME_WAIT | 时间等待 | 主动关闭方收到被动关闭方的FIN报文并回复ACK后,不立即关闭,而是等待2MSL(报文最大生存时间,通常1-2分钟) 的状态(核心作用:避免延迟报文干扰新连接)。 |
二、TCP 状态转移的三大核心阶段
TCP 状态转移并非孤立,而是围绕“连接建立-数据传输-连接关闭”形成闭环,以下分阶段解析(结合经典场景:客户端主动发起连接,主动关闭连接)。
阶段 1:三次握手(建立连接)—— 从 CLOSED 到 ESTABLISHED
此阶段是客户端与服务器通过 3 次报文交互,确认双方“发送/接收能力正常”,最终进入可传输数据的 ESTABLISHED 状态。
| 步骤 | 发起方 | 状态变迁 | 核心动作(报文) | 目的 |
| 1 | 客户端 | CLOSED → SYN_SENT | 发送SYN报文(同步序列编号,告诉服务器“我要连接,我的初始序号是X”) | 发起连接请求,测试服务器接收能力 |
| 2 | 服务器 | LISTEN → SYN_RCVD | 回复SYN+ACK报文(告诉客户端“我收到你的请求,我的初始序号是Y,确认你的序号X”) | 确认客户端请求,同时发起服务器的同步请求 |
| 3 | 客户端 | SYN_SENT → ESTABLISHED | 发送ACK报文(告诉服务器“我收到你的同步请求,确认你的序号Y”) | 最终确认服务器请求,客户端先进入连接状态 |
| 4 | 服务器 | SYN_RCVD → ESTABLISHED | 收到客户端ACK报文 | 服务器确认客户端最终响应,双方进入连接状态 |
关键说明:
- 服务器需先通过
socket()创建套接字,再调用listen()进入 LISTEN 状态,才能接收客户端连接; - 三次握手的核心是“避免因延迟的 SYN 报文建立无效连接”(如客户端旧 SYN 报文迟到,服务器若直接建立连接会浪费资源,三次握手可通过序号确认过滤无效请求)。
阶段 2:数据传输(连接维持)—— 稳定在 ESTABLISHED
双方进入 ESTABLISHED 状态后,可双向传输数据。此阶段状态不变,核心是通过“序号(Seq)”“确认号(Ack)”“滑动窗口”保证数据的可靠传输(如丢包重传、流量控制、拥塞控制)。
- 若某一方需暂时停止传输,会发送“窗口大小=0”的报文,对方进入“窗口关闭”等待状态;一旦窗口重新开放,恢复数据传输,状态仍为 ESTABLISHED。
阶段 3:四次挥手(关闭连接)—— 从 ESTABLISHED 到 CLOSED
TCP 是“双向连接”,关闭时需双方分别确认“不再发送数据”,因此需 4 次报文交互(因被动关闭方可能仍有数据未发送,需先回复 ACK,再延迟发送 FIN)。
| 步骤 | 发起方(主动关闭方,如客户端) | 被动关闭方(如服务器) | 核心动作(报文) | 状态变迁 |
| 1 | 客户端决定关闭连接 | - | 发送FIN报文(告诉服务器“我不再发数据了,你可以准备关闭”) | 客户端:ESTABLISHED → FIN_WAIT_1 |
| 2 | - | 服务器收到FIN | 回复ACK报文(告诉客户端“我收到你的关闭请求,会尽快处理”) | 服务器:ESTABLISHED → CLOSE_WAIT |
| 3 | 客户端收到ACK | 服务器处理完剩余数据后 | - | 客户端:FIN_WAIT_1 → FIN_WAIT_2(等待服务器的FIN) |
| 4 | - | 服务器发送FIN报文(告诉客户端“我也不再发数据了,你可以关闭”) | 服务器:CLOSE_WAIT → LAST_ACK | |
| 5 | 客户端收到FIN | - | 发送ACK报文(告诉服务器“我收到你的关闭请求,确认关闭”) | 客户端:FIN_WAIT_2 → TIME_WAIT |
| 6 | 客户端等待2MSL | 服务器收到ACK | - | 服务器:LAST_ACK → CLOSED(被动关闭方先完成关闭) |
| 7 | 客户端2MSL超时 | - | 无报文,直接释放资源 | 客户端:TIME_WAIT → CLOSED(主动关闭方完成关闭) |
关键说明:
为什么需要四次挥手?因为 TCP 是双向通信,关闭时需分别关闭“客户端 → 服务器”和“服务器 → 客户端”两个方向的连接:
- 第 1-2 步:关闭“客户端 → 服务器”的发送方向;
- 第 3-4 步:关闭“服务器 → 客户端”的发送方向。若服务器仍有数据未发送,会在 CLOSE_WAIT 状态中处理完数据,再发送 FIN,因此无法像三次握手那样将“SYN+ACK”合并为一次报文。
TIME_WAIT 状态的核心作用(最易误解的状态):
- 确保被动关闭方收到最终的 ACK(若 ACK 丢失,服务器会重发 FIN,客户端在 TIME_WAIT 内可再次回复 ACK);
- 避免延迟报文干扰新连接(2MSL 是报文在网络中最大生存时间,超时后网络中所有旧连接的报文都会失效,新连接不会被旧报文混淆)。
- 注意:TIME_WAIT 是主动关闭方的必经状态,若服务器频繁主动关闭连接(如短连接服务),会积累大量 TIME_WAIT 连接,需通过内核参数(如
net.ipv4.tcp_tw_reuse)优化。
三、特殊状态转移场景
除了“正常三次握手/四次挥手”,还存在两种特殊场景,对应 CLOSING 状态:
场景:双方同时发起关闭请求
若客户端和服务器在 ESTABLISHED 状态下,同时向对方发送 FIN 报文:
- 客户端发送 FIN 后,进入 FIN_WAIT_1,未收到 ACK 却先收到服务器的 FIN → 客户端状态变为 CLOSING;
- 服务器发送 FIN 后,进入 FIN_WAIT_1,未收到 ACK 却先收到客户端的 FIN → 服务器状态变为 CLOSING;
- 双方各自收到对方的 FIN 后,回复 ACK;
- 双方收到 ACK 后,均进入 TIME_WAIT,等待 2MSL 后关闭。
这种场景极少发生,CLOSING 状态是临时过渡状态,最终仍会通过 TIME_WAIT 走向 CLOSED。
四、TCP 状态转移总结图(简化版)
为直观理解,可参考以下简化的状态转移路径(以客户端视角为主):
CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
服务器视角的核心路径:
CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
五、常见问题与排查
大量 CLOSE_WAIT 状态的原因?
被动关闭方(如服务器)收到 FIN 后,回复 ACK 进入 CLOSE_WAIT,但应用层未调用close()释放连接,导致状态无法进入 LAST_ACK。需排查应用层代码(如资源泄漏、未处理关闭事件)。大量 TIME_WAIT 状态的影响?
每个 TIME_WAIT 状态会占用一个端口(客户端端口),若短连接频繁建立/关闭,会导致端口耗尽。解决方案:- 开启
net.ipv4.tcp_tw_reuse(允许 TIME_WAIT 状态的端口被复用); - 调整
net.ipv4.tcp_tw_timeout(缩短 TIME_WAIT 超时时间,不建议小于 1 分钟)。
- 开启
SYN_RCVD 状态堆积的原因?
服务器收到 SYN 后回复 SYN+ACK,但客户端未回复 ACK,导致服务器停留在 SYN_RCVD。可能原因:客户端异常退出、网络丢包、SYN 洪水攻击(需开启 TCP SYN Cookie 防御)。
