第一百一十六章:状态机
问道期涉及内核源码:
一
林小源在 TCP 的领地中行走,忽然发现脚下踩着的地面在不断变换颜色——从幽暗的黑,到明亮的蓝,再到暗红,最后归于沉寂。
"别乱踩。"一个低沉的声音从身后传来。
林小源回头,看见一个身披长袍的老者。那长袍上绣满了状态转换图——箭头从衣襟延伸到袖口,从领口蜿蜒到下摆,每一条线都标注着触发条件。老者的面容苍老,但双目锐利如鹰。
"你是?"
"tcp_rcv_state_process。"老者说,"我是状态机的守护者。你脚下的地面,每一个颜色代表连接的一个状态。"
林小源低头看去——地面像一块巨大的棋盘,每一格都是一种颜色。他正站在一格幽蓝的区域上,上面刻着"ESTABLISHED"。
"TCP 连接不是一直存在的。"老者缓步走着,长袍上的箭头随之流动,"它有出生,有成长,有衰老,有死亡。从 CLOSED 开始,经过三次握手到达 ESTABLISHED,然后在某个时刻走向 FIN_WAIT 或 CLOSE_WAIT,最终回到 CLOSED。"
"就像……一个人的一生?"
老者停下脚步,回头看了他一眼:"你这比喻倒也贴切。但人死不能复生,TCP 连接可以——同一个端口对可以重新建立连接,只是序列号不同了。"
二
林小源跟着老者走到了一片特殊的区域——地面呈现出暗淡的灰绿色,空气中弥漫着一种焦躁的气息。这里刻满了"TIME_WAIT"的符文。
"这是什么地方?"
"TIME_WAIT 领域。"老者的声音变得低沉,"主动关闭连接的一方会在这里等待——等 2MSL,大约 60 秒。"
林小源看到无数半透明的连接在这里漂浮,像是被困在琥珀中的昆虫。"为什么要等这么久?资源不是浪费了吗?"
老者蹲下身,捡起一个漂浮的 TIME_WAIT 连接,放在掌心:"你想想——如果最后那个 ACK 丢了,对方会怎样?"
"对方会重传 FIN。"
"对。如果没有 TIME_WAIT,连接已经关闭了,新连接可能收到旧 FIN。"老者把那连接放回空中,"TIME_WAIT 确保最后的 ACK 能到达对方,也防止旧连接的数据包干扰新连接。60 秒,是两倍的最大报文生存时间——足够让网络中残余的旧包消亡。"
林小源皱眉:"可我在服务器上看到几万个 TIME_WAIT 连接……"
"那是调优的问题。"老者站起身,"可以用 或调整 。但记住——TIME_WAIT 本身不是 bug,它是设计的一部分。"
三
他们继续前行,来到另一片区域。这里的气氛更加压抑——灰色的地面不断下沉,仿佛随时要塌陷。空气中回荡着一种低沉的嗡鸣声。
"这里是 CLOSE_WAIT。"老者的表情严肃起来。
林小源看到无数连接沉没在灰色的泥沼中,一动不动。"它们在等什么?"
"等应用程序关闭 socket。"老者的声音里带着一丝不满,"收到对方的 FIN 后,内核把连接放入 CLOSE_WAIT 状态,等应用程序调用 close()。但有些程序……就是不关。"
"程序 bug?"
"通常是。"老者指着那些沉没的连接,"如果你在服务器上看到大量 CLOSE_WAIT,十有八九是应用程序没有正确关闭 socket——可能是忘记调用 close(),可能是异常处理路径遗漏了。这是资源泄漏。"
林小源蹲下身,伸手触碰一个 CLOSE_WAIT 连接。它冰冷而沉重,像是被遗弃的锚。"那 LAST_ACK 呢?"
"被动关闭的一方发送 FIN 后进入 LAST_ACK,等待最后的 ACK。"老者指向远处一个短暂闪烁的区域,"那个状态很短暂——收到 ACK 就消失了。但 CLOSE_WAIT 不同,只要应用程序不关 socket,它就一直存在。"
老者转身看着林小源:"记住——状态机不是装饰,是连接的生命周期。每个状态都有存在的理由,每个转换都有触发条件。 根据当前状态和收到的报文决定下一步做什么。这是 TCP 的灵魂。"
/*
* TCP 状态:
*
* CLOSED — 没有连接
* LISTEN — 服务器等待连接
* SYN_SENT — 客户端已发送 SYN
* SYN_RECEIVED — 服务器已收到 SYN
* ESTABLISHED — 连接已建立
* FIN_WAIT_1 — 主动关闭,已发送 FIN
* FIN_WAIT_2 — 已收到 ACK
* TIME_WAIT — 等待 2MSL
* CLOSE_WAIT — 被动关闭,已收到 FIN
* LAST_ACK — 已发送 FIN,等待 ACK
*
* 连接建立:
* CLOSED → SYN_SENT → ESTABLISHED (客户端)
* CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED (服务器)
*
* 连接关闭:
* ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
* ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
*/
printf("=== TCP 状态机 — 连接的生命周期 ===\n\n");
printf("TCP 状态:\n\n");
printf("连接建立:\n");
printf(" CLOSED → SYN_SENT → ESTABLISHED (客户端)\n");
printf(" CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED (服务器)\n\n");
printf("连接关闭:\n");
printf(" ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED\n");
printf(" ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED\n\n");
printf("--- 状态转换图 ---\n");
printf(" CLOSED\n");
printf(" ↓ (connect)\n");
printf(" SYN_SENT\n");
printf(" ↓ (收到 SYN+ACK)\n");
printf(" ESTABLISHED\n");
printf(" ↓ (close)\n");
printf(" FIN_WAIT_1\n");
printf(" ↓ (收到 ACK)\n");
printf(" FIN_WAIT_2\n");
printf(" ↓ (收到 FIN)\n");
printf(" TIME_WAIT\n");
printf(" ↓ (2MSL 超时)\n");
printf(" CLOSED\n\n");
printf("--- TIME_WAIT ---\n");
printf("等待 2MSL (约 60 秒):\n");
printf(" 确保最后的 ACK 到达\n");
printf(" 防止旧连接的数据干扰新连接\n\n");
printf("--- CLOSE_WAIT ---\n");
printf("被动关闭方的状态:\n");
printf(" 收到对方的 FIN\n");
printf(" 等待本地关闭\n");
printf(" 大量 CLOSE_WAIT 可能是程序 bug\n\n");
printf("--- 查看 TCP 状态 ---\n");
printf("ss -tan\n");
printf(" ESTAB 100\n");
printf(" TIME-WAIT 50\n");
printf(" LISTEN 10\n");#include <stdio.h>
/*
* TCP 状态:
*
* CLOSED — 没有连接
* LISTEN — 服务器等待连接
* SYN_SENT — 客户端已发送 SYN
* SYN_RECEIVED — 服务器已收到 SYN
* ESTABLISHED — 连接已建立
* FIN_WAIT_1 — 主动关闭,已发送 FIN
* FIN_WAIT_2 — 已收到 ACK
* TIME_WAIT — 等待 2MSL
* CLOSE_WAIT — 被动关闭,已收到 FIN
* LAST_ACK — 已发送 FIN,等待 ACK
*
* 连接建立:
* CLOSED → SYN_SENT → ESTABLISHED (客户端)
* CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED (服务器)
*
* 连接关闭:
* ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
* ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
*/
int main() {
printf("=== TCP 状态机 — 连接的生命周期 ===\n\n");
printf("TCP 状态:\n\n");
printf("连接建立:\n");
printf(" CLOSED → SYN_SENT → ESTABLISHED (客户端)\n");
printf(" CLOSED → LISTEN → SYN_RECEIVED → ESTABLISHED (服务器)\n\n");
printf("连接关闭:\n");
printf(" ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED\n");
printf(" ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED\n\n");
printf("--- 状态转换图 ---\n");
printf(" CLOSED\n");
printf(" ↓ (connect)\n");
printf(" SYN_SENT\n");
printf(" ↓ (收到 SYN+ACK)\n");
printf(" ESTABLISHED\n");
printf(" ↓ (close)\n");
printf(" FIN_WAIT_1\n");
printf(" ↓ (收到 ACK)\n");
printf(" FIN_WAIT_2\n");
printf(" ↓ (收到 FIN)\n");
printf(" TIME_WAIT\n");
printf(" ↓ (2MSL 超时)\n");
printf(" CLOSED\n\n");
printf("--- TIME_WAIT ---\n");
printf("等待 2MSL (约 60 秒):\n");
printf(" 确保最后的 ACK 到达\n");
printf(" 防止旧连接的数据干扰新连接\n\n");
printf("--- CLOSE_WAIT ---\n");
printf("被动关闭方的状态:\n");
printf(" 收到对方的 FIN\n");
printf(" 等待本地关闭\n");
printf(" 大量 CLOSE_WAIT 可能是程序 bug\n\n");
printf("--- 查看 TCP 状态 ---\n");
printf("ss -tan\n");
printf(" ESTAB 100\n");
printf(" TIME-WAIT 50\n");
printf(" LISTEN 10\n");
return 0;
}道藏笔记
内核启示
TCP 状态机描述连接的生命周期。
TCP 状态:
- — 没有连接
- — 服务器等待连接
- — 连接已建立
- — 主动关闭中
- — 被动关闭中
- — 等待 2MSL
TIME_WAIT:
- 等待 2MSL(约 60 秒)
- 确保最后的 ACK 到达
- 防止旧数据干扰新连接
状态机是"连接"的"生命线"——每个状态都有意义。
状态机之试
状态机一章里,应用迟迟不关闭 socket 时容易堆积的 TCP 状态是什么?