TCP 状态机 / 滑动窗口 / 拥塞控制

TCP 之所以可靠,靠的是三套机制:状态机管连接生命周期、 滑动窗口管流量控制、拥塞控制管网络健康。 下面用三张可交互视图,把这三个抽象机制变成可以亲手操作的东西。

11 状态机 三次握手 / 四次挥手 滑动窗口流量控制 慢启动 · 拥塞避免 快速重传 · 快速恢复

1公式与参数

滑动窗口的吞吐量由窗口大小 $W$ 与往返时延 $\text{RTT}$ 共同决定。 这是"带宽时延积"(BDP)概念的来源:

$$\text{吞吐量} = \frac{W}{\text{RTT}} \quad (\text{bytes/s})$$
$$W_{\text{opt}} = \text{BDP} = \text{带宽} \times \text{RTT}$$

如果窗口小于 BDP,链路就填不满;如果远大于 BDP,只会白白增加排队时延。最优窗口恰好等于"管道容量"。

拥塞控制的核心是维护一个拥塞窗口 $\text{cwnd}$,实际发送窗口取它与接收窗口的较小值:

$$W_{\text{send}} = \min(\text{cwnd},\ \text{rwnd})$$

$\text{cwnd}$ 由发送方根据网络状况动态调整,$\text{rwnd}$ 由接收方通告。二者取小,既防拥塞又防溢出。

慢启动阶段 $\text{cwnd}$ 每经过一个 RTT 就翻倍(指数增长); 达到慢启动阈值 $\text{ssthresh}$ 后转入拥塞避免,每 RTT 只加 1:

$$\text{cwnd} \leftarrow \text{cwnd} \times 2 \quad (\text{慢启动})$$
$$\text{cwnd} \leftarrow \text{cwnd} + 1 \quad (\text{拥塞避免})$$

检测到丢包时,TCP 根据信号类型采取不同策略。经典 Tahoe 与 Reno 的差异:

$$\text{超时:}\quad \text{ssthresh} \leftarrow \frac{\text{cwnd}}{2},\ \text{cwnd} \leftarrow 1$$ $$\text{快速重传:}\quad \text{ssthresh} \leftarrow \frac{\text{cwnd}}{2},\ \text{cwnd} \leftarrow \text{ssthresh} + 3$$

超时是"重度拥塞"信号,cwnd 直接回到 1;收到 3 个重复 ACK 说明只是零星丢包,快速重传只把 cwnd 减半。

符号 数学含义 通俗解释
cwnd 拥塞窗口 发送方自己估计"网络还能承受多少"。是 TCP 拥塞控制的核心变量。
rwnd 接收窗口 接收方通告"我缓冲区还能放多少"。防止发送方把接收方撑爆。
ssthresh 慢启动阈值 cwnd 超过它就从"翻倍"切到"每次 +1"。是一种保守化的分界线。
RTT 往返时延 一个报文从发出到收到确认的时间。是 TCP 调整速度的基本节拍。
MSS 最大报文段长度 一个 TCP 段最多能装多少数据。窗口大小通常以 MSS 为单位计数。
BDP 带宽时延积 链路上"在途"的比特总量。窗口要接近它才能跑满带宽。
2MSL 两倍报文最大生存时间 TIME_WAIT 状态要等的时长,确保最后一个 ACK 到达对方。
ACK 确认号 告诉对方"这个序号之前的我全收到了"。滑动窗口靠它向前推进。
SYN / FIN 连接建立 / 关闭标志 SYN 用来发起连接,FIN 用来请求关闭。它们各消耗一个序号。
RST 重置 强制中断连接。收到 RST 通常意味着对方拒绝或异常。

2交互模拟

从 CLOSED 出发,点击左侧事件按钮触发状态转换。 图中的高亮节点是当前状态,黄色边表示从当前状态可用的转换。 完整走一遍"主动打开 → 数据传输 → 主动关闭"能覆盖大部分状态。

状态: CLOSED
提示:三次握手 = 主动打开 → 收到SYN+ACK → 收到ACK; 四次挥手 = 主动关闭 → 收到ACK → 收到FIN → 超时2MSL。
当前状态
CLOSED
转换步数
0
已访问状态
1 / 11
已完成握手
否
转换日志将显示在这里…

每个方块代表一个序号。发送方把窗口内的报文发出去,收到 ACK 后窗口向右滑动。 黄色是"还能发但还没发",蓝色是"已发等 ACK",绿色是"已被确认"。

模拟要发送的数据总量(以 MSS 为单位)。
发送方最多能同时"在途"的报文数。越大吞吐越高。
数值越大,每步等待越久,越容易看清窗口滑动。
已发送
0
已确认
0
在途
0
窗口利用率
0.00%

序列号空间

已确认 已发送未确认 可发送 窗口外
窗口范围
[0, 4)
发送/确认日志将显示在这里…

点「开始模拟」让 cwnd 随时间演化。注意曲线从陡峭到平缓的变化—— 这就是从慢启动切换到拥塞避免的时刻。 设置丢包发生的 RTT,观察 cwnd 如何应声而落。

慢启动的"天花板"。cwnd 到达它之后就转为线性增长。
模拟网络在第几个 RTT 丢包。设为 30 则整段不丢包。
超时是重度拥塞,快速重传只减半。
每个 RTT 之间的等待时间。
当前 RTT
0
当前 cwnd
1.00
当前 ssthresh
16.00
丢包次数
0
当前阶段: 未开始
点击「开始模拟」观察 cwnd 的演化。

3深入理解

标准介绍

TCP 由 Cerf 与 Kahn 于 1974 年提出,1981 年 RFC 793 正式标准化。 状态机定义了连接从建立到关闭的 11 个状态及其转换规则, 是 TCP 连接管理的骨架。 滑动窗口由接收方通告窗口(rwnd)实现端到端流量控制, 防止发送方压垮慢速接收方。 拥塞控制由 Jacobson 于 1988 年引入(Tahoe), Reno(1990)增加了快速重传与快速恢复, 后续还有 NewReno、CUBIC、BBR 等演进版本。 这三套机制共同构成了 TCP 可靠传输的基础。

通俗介绍

状态机像打电话的固定流程: 拨号(SYN)→ 对方接听(SYN+ACK)→ 说"喂"(ACK)→ 通话 → 说"拜拜"(FIN)→ 挂断。 每个阶段都有一个状态名,比如"正在拨号中"、"通话中"、"正在挂断中"。 滑动窗口像快递发货:你有一个仓库(序列号空间), 一次最多装一车货(窗口大小),每发一车就等对方签收单。 签收单陆续回来,车子才能继续往前开。 拥塞控制像开车时看路况: 刚上路时不知道堵不堵,先试着加速(慢启动,翻倍); 快到拥堵路段了,就小心翼翼地每次只加一点(拥塞避免); 一旦真堵了(丢包),立刻减速——小事故减半(快速重传), 大事故急刹(超时,退回起点)。

解决了什么问题

  • 可靠传输:通过序号、确认、重传,保证数据不丢不重不乱序。
  • 连接管理:状态机让双方对"连接处于什么阶段"有一致认知。
  • 流量控制:滑动窗口防止快发送方压垮慢接收方。
  • 拥塞控制:cwnd 动态调整防止网络整体崩溃(1986 年的拥塞崩溃事件推动了它)。
  • 双向对称:同一套机制同时处理发送和接收,两端对等。

为什么重要

  • 它是互联网的基石协议:HTTP、HTTPS、SMTP、SSH、FTP 全都建立在 TCP 之上。
  • 它是网络课程的核心章节:状态机、滑动窗口、拥塞控制是计算机网络三大必考内容。
  • 它是端到端原则的典范:把复杂性放在端点,网络本身保持简单。
  • 它是控制论的工程实践:用反馈信号动态调整发送速率,是控制系统思想的经典应用。
  • 它的演进持续了 40 多年:从 Tahoe 到 BBR,从未停止优化。

应用场景

  • Web 浏览:HTTP/1.1、HTTP/2 都基于 TCP,页面加载速度受 TCP 参数直接影响。
  • 文件传输:FTP、SFTP 依赖 TCP 的可靠传输。
  • 电子邮件:SMTP、IMAP 使用 TCP 保证邮件不丢。
  • 远程登录:SSH、Telnet 需要有序的双向字节流。
  • 数据库连接:MySQL、PostgreSQL 的客户端协议都跑在 TCP 上。
  • CDN 加速:通过调整初始 cwnd、启用 TCP Fast Open 来降低首包时延。
  • 数据中心:DCTCP、CUBIC 等针对短 RTT、高带宽场景优化的拥塞控制算法。

缺陷与局限

  • 队头阻塞:TCP 是严格有序的字节流,一个丢包会阻塞后续所有数据交付给应用层。
  • 握手延迟:三次握手至少消耗 1.5 个 RTT,短连接场景开销显著。
  • TIME_WAIT 占用资源:高并发短连接服务器上大量 TIME_WAIT 会耗尽端口。
  • 拥塞控制对无线网络不友好:把无线误码当成拥塞信号,导致不必要的降速。
  • 易受攻击:SYN Flood、RST 注入等攻击针对状态机和握手流程。
  • 难以支持多路径:传统 TCP 绑定单一路径,MPTCP 的部署仍在推进中。
  • 不适合实时应用:重传机制会引入不确定延迟,音视频更倾向 UDP/QUIC。

动手建议

在状态机里走一遍完整的"客户端主动关闭"流程: CLOSED →(主动打开)→ SYN_SENT →(收到SYN+ACK)→ ESTABLISHED → (主动关闭)→ FIN_WAIT_1 →(收到ACK)→ FIN_WAIT_2 →(收到FIN)→ TIME_WAIT → (超时2MSL)→ CLOSED。数一数一共经历了几次状态转换,其中哪些状态会"等待"。 在滑动窗口里把窗口从 2 调到 7: 窗口越大,同时在途的报文越多,观察"窗口利用率"的稳定值是否接近 100%。 然后把 N 调到 16,看完整跑完需要多少步——步骤数大致等于 N 加上窗口的"预热"步数。 在拥塞控制里把 ssthresh 设为 8,丢包 RTT 设为 10,类型选"超时": 你会看到 cwnd 从 1 开始指数增长到 8,然后线性增长,到第 10 个 RTT 时骤降到 1, ssthresh 变成大约 cwnd 的一半。再改成"快速重传", cwnd 只降到 ssthresh+3 而不是 1——这就是 Reno 比 Tahoe 更高效的原因。