Skip to content

第一百一十六章:状态机

问道期

涉及内核源码:

林小源在 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 的灵魂。"

c
/*
 * 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");

道藏笔记

内核启示

TCP 状态机描述连接的生命周期。

TCP 状态:

  • — 没有连接
  • — 服务器等待连接
  • — 连接已建立
  • — 主动关闭中
  • — 被动关闭中
  • — 等待 2MSL

TIME_WAIT:

  • 等待 2MSL(约 60 秒)
  • 确保最后的 ACK 到达
  • 防止旧数据干扰新连接

状态机是"连接"的"生命线"——每个状态都有意义。


破关试炼

状态机之试

状态机一章里,应用迟迟不关闭 socket 时容易堆积的 TCP 状态是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道