← 网络基础学习第 20 / 55 章进阶40 分钟

模块二 · 运输层

滑动窗口与流量控制

如果 TCP 每发一个段都要等对方确认,那么不管带宽多大,实际速度都会被"等待时间"锁死。本章讲 TCP 怎么用滑动窗口解决这个问题,以及"流量控制"到底在控制什么。

本章要解决的问题:如果 TCP 每发一个段都要等对方确认,那么不管带宽多大,实际速度都会被"等待时间"锁死。本章讲 TCP 怎么用滑动窗口解决这个问题,以及"流量控制"到底在控制什么。

本章的证据来源:历史实测记录(Get-NetTCPSetting 与 netsh int tcp show global 的窗口相关参数)。机制出自 RFC 9293(TCP)与 RFC 7323(窗口缩放)。

先从一个例子理解概念

如果每发一小份都要等回应,长距离下载会很慢;但一口气全发,又可能让接收端来不及处理。窗口用来管理能同时在路上的量。

未确认数据有多少:在途数据与窗口

已发出但尚未得到相应确认的内容叫在途数据。窗口限制这类内容的数量,常按字节计;它不是屏幕上的窗口。

对方还能收多少:接收窗口与流量控制

接收端通告可接收范围,叫接收窗口(rwnd)。发送端遵守它,以免压垮对方的缓冲,这叫流量控制。缓冲区是暂存待处理数据的内存。

网络能承受多少:拥塞窗口

发送端还会根据网络反馈维护拥塞窗口(cwnd)。rwnd管接收端,cwnd管路径负载;本次新发量还要扣掉已在途数据。

把较大窗口写进有限字段:窗口缩放

TCP窗口字段大小有限,双方可以协商用一个移位因子解释更大范围,叫窗口缩放。这需要双方支持,不是本机开关一开就能单方面生效。

下面再用这些概念看原理、命令和结果。规范与官方资料列在本章末尾,类比只帮助理解,不代替协议规则。

先算一笔账:停等协议有多慢

假设 TCP 是这样工作的:发一个段 → 等确认回来 → 再发下一个。

这叫停等协议,问题立刻就能算出来。

举例:你的网络往返时间(RTT)是 100 毫秒,每个段装 1500 字节。

每发出一段要等一个完整的往返:100 毫秒
所以每秒最多发:1000 ÷ 100 = 10 个段
吞吐量上限:10 × 1500 字节 ≈ 15 KB/s

15 KB/s。 也就是说,哪怕你用的是千兆宽带,速度也只有 15 KB/s。

滑动窗口:一次发一批,不用等

解决办法很直接:不要发一个等一个,而是一次发一批。

发送方维护一个窗口(window),表示"我现在可以连续发出多少字节而不用等确认":

发送窗口(比如 4 个段):

  [ 段1 ][ 段2 ][ 段3 ][ 段4 ]                          还没发
   已确认                         ↑
                             可以发的范围

发出段 1~4 之后,收到 ACK 确认了段 1、段 2,窗口就向右滑动:

  [ 段1 ][ 段2 ]  [ 段3 ][ 段4 ][ 段5 ][ 段6 ]
   已确认            还在等确认      新进入窗口的
                 ↑
           窗口向右滑了

关键点:窗口内可以有多少数据"在路上",决定了吞吐能力。

吞吐量 ≈ 窗口大小 ÷ RTT

用前面的例子:如果窗口能装 20 个段(30000 字节),RTT 是 100ms:

30000 字节 ÷ 0.1 秒 = 300 KB/s

比停等协议的 15 KB/s 快了 20 倍。 窗口越大,能跑满的带宽越高。

接收窗口:这就是"流量控制"

窗口大小不能由发送方随便定。接收方可能会处理不过来 —— 它的内存有限,如果发送方猛发,缓冲区满了,数据只能被丢弃,丢了又要重传,反而更糟。

所以接收方在每一个 ACK 里都告诉发送方:"我还能收多少字节。" 这个字段叫接收窗口(rwnd)。

发送方能发多少 = min( 发送方的拥塞窗口 , 接收方通告的接收窗口 )
                     ↑ 下一章讲          ↑ 本章的主角

"流量控制"控制的就是这个接收窗口 —— 目的是不要压垮对方。

窗口字段只有 16 位,这就出了问题

TCP 头里的窗口字段是 16 位,所以它最大只能表示:

2¹⁶ − 1 = 65535 字节(约 64 KB)

再算一次账:窗口最多 64 KB,RTT 100 毫秒:

65535 字节 ÷ 0.1 秒 ≈ 655 KB/s

655 KB/s。 在上世纪 80 年代这很快,但今天连手机网络都不止这个速度。

问题不在于"窗口只有 64KB",而在于"窗口不够大就填不满带宽延迟积"。 一条 1 Gbps、RTT 100ms 的链路,需要大约 12.5 MB 的在途数据才能跑满 —— 64 KB 差了 200 倍。

解决办法:窗口缩放

RFC 7323(早期是 RFC 1323)定义了一个选项:窗口缩放因子。

双方在SYN/SYN+ACK中交换方向相关的移位因子0–14;协商成功后的非SYN窗口按对应2ⁿ缩放:

通告的窗口值 65535 × 2¹⁴ ≈ 1 GB

这样窗口上限从 64 KB 提到了约 1 GB,足够填满今天绝大多数链路。

实测:本机的窗口设置powershell
Get-NetTCPSetting

实测结果:

参数实测值含义
AutoTuningLevelLocalNormal接收窗口自动调优(默认档)
ForceWSEnabled强制启用窗口缩放
ScalingHeuristicsDisabled不使用启发式的缩放判断
RFC 1323 Timestampsallowed允许使用时间戳选项(和窗口缩放同一批扩展)

AutoTuningLevelLocal = Normal 是什么意思?

它表示内核会根据实际吞吐情况自动调整接收窗口:发现连接跑得快就调大,发现用不上就调小(省内存)。这是现代系统的常规做法 —— 不需要人工调优,系统自己会找合适的值。

ForceWS是本地策略,不能保证窗口缩放一定协商成功。 RFC7323要求双方在SYN报文交换窗口缩放选项;对端不支持时不能单方面放大。SYN自身窗口字段不缩放。

零窗口:接收方喊停

如果接收方的缓冲区真的满了,它会通告一个窗口大小为 0。

发送方收到之后必须停止发送。但这里有个陷阱:

所以发送方在收到零窗口后不会干等 —— 它会定期发送窗口探测(window probe):一个只带 1 个字节的包,试探对方现在的窗口是多少。

这个细节体现了一个通用原则:协议设计必须考虑到"通知消息本身也可能丢失"。

实测:网卡还有一层合并

除了协议层的窗口,本机的网卡还开着一个优化:

Receive Segment Coalescing State : enabled

接收段合并(RSC):硬件或软件实现把连续到达的多个段合并成一个大块再交给协议栈。

好处:减少中断次数和协议栈的处理开销,相当于把"多次小批量处理"变成"一次大批量处理"。这和滑动窗口的思想是一致的 —— 批量处理总是比逐个处理高效。

常见误解

误解一:"窗口大小就是带宽"

不是。窗口大小决定的是"同时在路上"的数据量上限。实际吞吐 ≈ 窗口 ÷ RTT。所以同样的窗口,在低延迟链路上能跑出的吞吐更高。这也是为什么"远距离传输"特别依赖大窗口。

误解二:"窗口字段只有 16 位,所以 TCP 最多只能跑到 655 KB/s"

这是老黄历了。 在RTT=100ms且无缩放的示例条件下约为此值;不是任何RTT下都固定655KB/s,但窗口缩放选项(RFC 1323/7323)把上限提到了约 1 GB。现代系统默认启用(历史实测记录 ForceWS = Enabled)。

误解三:"流量控制和拥塞控制是一回事"

不是。 流量控制解决"接收方处理不过来",拥塞控制解决"网络承受不了"。前者靠接收方通告窗口,后者靠发送方自己估算。在途未确认数据的窗口上限受两者较小值约束;本次还能新发多少要扣除已在途量,并受应用数据和配速等限制。

误解四:"接收方通告了窗口,发送方就可以立刻按这个量发"

不一定。发送方还要受自己的拥塞窗口限制(下一章)。接收方说"我能收 1MB",但如果发送方估算网络只能承受 64KB,那还是只能发 64KB。

本章小结

结论类型
停等协议下吞吐 ≈ 段大小 ÷ RTT,带宽再大也跑不满推理
带宽延迟积 = 带宽 × 延迟,决定链路能容纳多少在途数据标准
滑动窗口让发送方连续发送多个段而不必逐个等待确认标准
吞吐量 ≈ 窗口大小 ÷ RTT推理
接收窗口由接收方在 ACK 中通告,用于流量控制标准
在途上限受min(cwnd,rwnd)约束,新增可发量还需扣除在途量协议范围
窗口字段 16 位,上限 64 KB,不足以填满现代高带宽链路标准
窗口缩放(RFC 7323)把窗口上限提到约 1 GB标准
实测:本机 AutoTuningLevelLocal = Normal(接收窗口自动调优)实测
实测:本机 ForceWS = Enabled(强制启用窗口缩放)实测
零窗口时发送方定期发窗口探测,避免双方死锁标准
实测:本机 Receive Segment Coalescing = enabled(网卡合并入站段)实测

自测题

6 题 · 先自己答,再展开对照

  1. 网络 RTT 是 50 毫秒,窗口大小是 64 KB。理论吞吐上限大约是多少?
    约 1.3 MB/s。 65535 字节 ÷ 0.05 秒 ≈ 1.31 MB/s。注意这个上限和你的实际带宽无关 —— 哪怕你用的是万兆网络,只要窗口只有 64 KB、RTT 是 50ms,吞吐就被锁在 1.3 MB/s。 这就是"带宽延迟积"的约束。
  2. 如果不做窗口缩放,一条 RTT 200ms 的跨国链路最多能跑多快?为什么这不够用?
    约 327 KB/s(65535 ÷ 0.2)。这个速度远远不够用 —— 一条普通的百兆链路理论上有 12.5 MB/s,但窗口限制让实际只能跑到 3% 都不到。跨国链路 RTT 大,需要的窗口更大,所以窗口缩放对长距离传输尤其关键。
  3. "流量控制"和"拥塞控制"分别要解决什么问题?它们各自用什么手段?
    流量控制解决"接收方处理不过来"(对方内存/处理能力有限,发太猛会导致丢包重传,反而更糟),手段是接收方在 ACK 里通告接收窗口。
    拥塞控制解决"网络中间承受不了"(链路带宽有限,发太猛会让路由器队列溢出、丢包加剧),手段是发送方自己估算一个拥塞窗口并动态调整。
    在途未确认数据的窗口上限受两者较小值约束;本次还能新发多少要扣除已在途量,并受应用数据和配速等限制。
  4. 接收方通告窗口为 0,发送方应该怎么做?为什么不能就这么一直等下去?
    立刻停止发送,但要定期发送窗口探测(只带 1 字节的报文)去询问对方当前的窗口大小。不能干等着,因为接收方腾出空间后发出的"窗口更新"通知可能丢失 —— 如果双方都在等对方,就会永远僵住(死锁)。定期探测是打破这个僵局的手段。
  5. 窗口缩放是怎么把上限从 64 KB 提到 1 GB 的?
    双方在初始SYN交换选项,分别提供方向相关的缩放因子0–14;非SYN窗口按相应因子缩放,最大65535×2¹⁴字节。本地ForceWS不能代替对端协商。
  6. 为什么同样大小的窗口,在低延迟链路上能跑出更高的吞吐?
    因为吞吐 ≈ 窗口 ÷ RTT。 窗口表示"同时能在路上的数据量",而数据跑完一个往返需要 RTT 的时间。RTT 越小,同样的数据量周转越快,单位时间能送出的就越多。这就是为什么低延迟对网络性能如此重要 —— 它和带宽是两个独立的维度,任何一个不够都会成为瓶颈。

参考资料与适用范围

修订核对日期:2026-10-13。规则按所列协议与实现范围理解;文中的原始命令输出是历史观察,不表示本次重新实测。跨版本、代理/隧道及不同捕获位置需要另行核对。

正文源自 m2-05-滑动窗口与流量控制.md · 共 11 节 · 6 道自测题