TCP 滑动窗口是接收方向发送方通告「我还能接收多少字节」的机制,防止发送方淹没接收方缓冲区;与拥塞控制的 cwnd 共同决定有效发送速率,是 TCP 可靠传输的基础组成部分。
目录
| 章节 | 说明 |
|---|---|
| 流量控制 vs 拥塞控制 | 两者的本质区别 |
| 滑动窗口模型 | 四个缓冲区区域,三个指针 |
| 窗口通告机制 | ACK 携带 rwnd,Zero Window 死锁 |
| Window Scaling 扩展 | 高带宽延迟网络的 16bit 上限突破 |
| Silly Window Syndrome | 小窗口综合征及 Nagle/Clark 解法 |
| 有效窗口计算 | min(rwnd, cwnd) 实际控制逻辑 |
| 参考资料 | RFC 与文档 |
流量控制 vs 拥塞控制
TCP 有两套独立的速率控制机制,经常被混淆:
| 维度 | 流量控制(rwnd) | 拥塞控制(cwnd) |
|---|---|---|
| 保护对象 | 接收方缓冲区 | 网络链路 |
| 由谁决定 | 接收方(广播 rwnd) | 发送方(本地估算) |
| 信号来源 | ACK 中的 Window 字段 | 丢包 / ECN / 延迟变化 |
| 算法 | 简单通告,无须估算 | CUBIC、BBR 等复杂算法 |
| RFC | RFC 793 | RFC 5681(Reno)等 |
有效发送窗口 = min(rwnd, cwnd):发送方受两者中较小值约束。
滑动窗口模型
发送方缓冲区的四个区域
|<── 已发送已确认 ──>|<── 已发送未确认 ──>|<── 可发送未发送 ──>|<── 窗口外 ──>|
↑ ↑ ↑
SND.UNA SND.NXT SND.UNA + rwnd
| 区域 | 状态 | 可操作性 |
|---|---|---|
| 已发送已确认 | 数据已到达接收方 | 可释放缓冲 |
| 已发送未确认 | 在途,等待 ACK | 必须保留(重传用) |
| 可发送未发送 | 窗口内,待发 | 可立即发送 |
| 窗口外 | 超出 rwnd | 必须等待窗口滑动 |
三个关键指针(RFC 793)
| 指针 | 含义 |
|---|---|
SND.UNA |
最早未确认的字节序号(窗口左边界) |
SND.NXT |
下一个待发字节序号 |
SND.WND |
当前发送窗口大小(= rwnd) |
窗口滑动:每收到一个 ACK,SND.UNA 前移,窗口向右滑动,释放空间让新数据进入可发送区域。
接收方缓冲区
|<── 已消费 ──>|<── 已接收未消费 ──>|<── 空闲(可接收)──>|<── 超出 ──>|
↑ ↑
RCV.NXT RCV.NXT + rwnd
rwnd = 空闲缓冲大小 = 总缓冲 − 已接收未消费字节数
应用层每次 read() 消费数据后,rwnd 增大,通过下一个 ACK 通告给发送方。
窗口通告机制
正常流程
发送方 接收方
│── DATA(seq=1, len=1000) ────────→│ 缓冲: 4096→3096
│── DATA(seq=1001, len=1000) ──→ │ 缓冲: 3096→2096
│← ACK(ack=2001, rwnd=2096) ───────│ 通告剩余缓冲
│(窗口右移,SND.UNA=2001) │
│ │ 应用读取 2096 字节
│← ACK(ack=2001, rwnd=4096) ───────│ rwnd 恢复
Zero Window 与持续定时器
当接收方缓冲满时发送 rwnd=0:
发送方 接收方
│← ACK(ack=X, rwnd=0) ─────────────│ 缓冲满,停止接收
│ (停止发送) │
│ 持续定时器触发(通常 1~60s) │
│── Zero Window Probe ────────────→│ 探测包(1字节)
│← ACK(ack=X, rwnd=0) ─────────────│ 仍然满
│ (指数退避重试) │
│ │ 应用消费数据
│← ACK(ack=X, rwnd=4096) ──────────│ 窗口重新打开
死锁风险:若
rwnd=0的通告包丢失,发送方永久等待——持续定时器(Persist Timer)正是为此设计的防死锁机制。
Window Scaling 扩展
TCP header 的 Window 字段只有 16 bit,最大值 65535 字节(64KB)。
在高带宽延迟网络(如 10Gbps 跨洲链路,RTT=100ms)下:
带宽延迟积(BDP)= 10Gbps × 100ms = 125MB
64KB 远小于 125MB,链路利用率不足 0.05%。
解决方案:RFC 1323 定义 Window Scaling 选项(三次握手时协商):
实际窗口大小 = Window 字段值 × 2^shift_cnt
shift_cnt ∈ [0, 14],最大窗口 = 65535 × 2^14 ≈ 1GB
Linux 默认启用 Window Scaling(net.ipv4.tcp_window_scaling=1)。
Silly Window Syndrome
问题描述
发生场景:接收方应用层每次只读 1 字节,导致:
① 接收方缓冲空出 1 字节 → 通告 rwnd=1
② 发送方发送 1 字节数据(40 字节 TCP/IP header + 1 字节数据)
③ 98% 带宽浪费在头部
解决方案
接收方(Clark 算法):rwnd=0 后,等到缓冲空出的空间 ≥ min(MSS, 缓冲总量/2) 时再通告非零 rwnd(RFC 1122 §4.2.3.3)。
发送方(Nagle 算法):
if 待发数据 >= MSS 或 所有在途数据已确认:
立即发送
else:
等待(积累数据或等 ACK 回来)
交互式应用(SSH、游戏)通常禁用 Nagle(
TCP_NODELAY)以降低延迟。
有效窗口计算
cwnd:发送方根据网络状况估算的拥塞窗口
rwnd:接收方通告的接收窗口
effective_window = min(rwnd, cwnd)
实际可发送字节数 = effective_window − (SND.NXT − SND.UNA)
↑
已发未确认字节数
典型瓶颈场景:
| 场景 | 瓶颈 | 现象 |
|---|---|---|
| 接收方内存不足 | rwnd 小 | 发送速率受接收方限制 |
| 网络拥塞 | cwnd 小 | 发送速率受网络限制 |
| 长肥管道 | 两者都大但 BDP 需要大窗口 | 需要 Window Scaling |
参考资料
参考资料
- RFC 793 — Transmission Control Protocol(TCP 原始规范,滑动窗口定义)
- RFC 1323 — TCP Extensions for High Performance(Window Scaling)
- RFC 9293 — Transmission Control Protocol (TCP)(2022 年最新整合版)
- TCP 拥塞控制(CUBIC 与 BBR)(拥塞控制 cwnd 的对应算法)
评论 (0)