专栏文章
专栏文章
网络算法系列
1. 网络算法 #01:轮询与加权轮询 2. 网络算法 #02:最小连接数 3. 网络算法 #03:Jump Consistent Hash 4. 网络算法 #04:Rendezvous Hashing 5. 网络算法 #05:TCP 拥塞控制(CUBIC 与 BBR) 6. 网络算法 #06:QUIC 丢包恢复与拥塞控制 7. 网络算法 #07:经典一致性哈希(虚拟节点) 8. 网络算法 #08:P2C(Power of Two Choices) 9. 网络算法 #09:TCP 滑动窗口与流量控制 10. 网络算法 #10:Maglev Hashing

网络算法 #09:TCP 滑动窗口与流量控制

发布于 2026-06-08 09:02 👁 12 次阅读
#网络算法#TCP#流量控制#滑动窗口

TCP 滑动窗口是接收方向发送方通告「我还能接收多少字节」的机制,防止发送方淹没接收方缓冲区;与拥塞控制的 cwnd 共同决定有效发送速率,是 TCP 可靠传输的基础组成部分。

相关文章TCP 拥塞控制(CUBIC 与 BBR)

tcp sliding window

目录

章节 说明
流量控制 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)

暂无评论,来留下第一条吧。
登录注册 后才能发表评论