滑动窗口与流量控制:ARQ 效率、序号空间与 TCP/QUIC 的接收窗口

💡 原文中文,约27100字,阅读约需65分钟。
📝

内容提要

文章辨析TCP滑动窗口的三层含义,重点讨论可靠传输窗口与流量控制。通过时隙模型推导停等、GBN、SR的效率,指出SR窗口需达2倍BDP才能避免丢包阻塞,并证明序号空间上界为窗口不超过一半。结合Linux源码核对TCP、HTTP/2、QUIC的接收窗口与自动调优机制。

🔎

延伸解读

SR窗口需2倍BDP:丢包下的隐藏代价

文章通过时隙模型和模拟指出,选择重传(SR)在窗口等于BDP时,一旦发生丢包,发送方会因等待重传确认而阻塞约一个RTT,导致效率远低于理想值。模拟显示,丢包率1%时,SR效率仅0.717,而窗口增至2倍BDP后可达0.984。这解释了Linux接收缓冲自动调优中“2倍因子”的由来:为应对丢包,接收窗口需额外覆盖一个RTT的乱序数据。因此,仅按BDP配置缓冲区,在丢包环境下可能严重限制吞吐。

序号空间上界:窗口为何不能超过一半

文章证明,对于选择重传,发送窗口和接收窗口之和不能超过序号空间大小,即W ≤ M/2。若违反,在ACK全部丢失的最坏情况下,接收方可能将重传的旧帧误认为新帧,导致数据错位。模拟验证了边界:2位序号下,SR窗口为2时无错误,窗口为3时很快出现错误交付。TCP的32位序号通过有符号比较将有效空间折半,窗口缩放移位数因此限制为14,与这一理论一致。

Nagle与延迟ACK:40ms延迟的根源与修复

文章用实验复现了“写-写-读”模式下的性能问题:客户端先发小段头,再发小段体,Nagle算法扣住第二段等待第一段ACK,而服务器因未收全请求而延迟ACK,导致每个请求增加约40ms延迟。实测中位数约44ms,与Linux最小延迟ACK定时器(40ms)同量级。修复方法包括关闭Nagle(TCP_NODELAY)或将两次写合并为一次,后者不增加小包数量,是更优解。

HTTP/2与QUIC流控:信用机制的差异与陷阱

HTTP/2和QUIC都采用信用制流控,但实现不同:HTTP/2的WINDOW_UPDATE携带增量,依赖TCP可靠传输;QUIC的MAX_DATA和MAX_STREAM_DATA使用绝对偏移,能容忍控制帧丢失或乱序。两者都有流级和连接级窗口,任何一层过小都会成为瓶颈。常见误读是认为HTTP/2流控会影响cwnd,实际上规范未定义这种耦合,但流控导致连接空闲可能触发cwnd重启。QUIC并未去掉双层流控,只是消除了传输层队头阻塞。

❓

Q&A

为什么选择重传(SR)的窗口需要达到2倍BDP才能避免丢包阻塞?

因为当窗口等于BDP(K)时,发送方在丢失帧被确认前无法发送新数据,导致等待约K个时隙。要让单次丢失不阻塞发送,窗口必须覆盖“丢失、发现、重发、确认”两个RTT,即W≥2K。Linux的tcp_rcv_space_adjust()中也有2倍因子注释。

TCP序号空间为什么限制窗口不能超过一半?

因为TCP使用32位序号,但比较时用有符号差值,有效序号空间为2^31。为避免旧帧被误认为新帧,必须满足发送窗口+接收窗口≤2^31,所以最大窗口小于2^30。窗口缩放移位数因此不得超过14。

HTTP/2和QUIC的流控机制有什么主要区别?

HTTP/2使用增量式的WINDOW_UPDATE,依赖TCP可靠传输,窗口更新不会丢失或乱序;QUIC使用绝对偏移的MAX_DATA和MAX_STREAM_DATA,能容忍控制帧丢失、重复或乱序,发送方忽略不提高上限的帧。两者都有流级和连接级两层流控。

Nagle算法与延迟ACK交互为什么会导致约40ms的延迟?

在“写-写-读”模式中,第一个小段立即发出,第二个小段被Nagle扣住等待第一个段的ACK;服务器未收到完整请求,无法捎带ACK,只能等延迟ACK定时器超时(Linux最小40ms)。每个请求因此多出一个定时器周期。

Linux接收缓冲自动调优是如何工作的?

tcp_rcv_space_adjust()每个RTT估计一次应用读取的字节数(copied),计算rcvwin = 2*copied + 16*advmss,并考虑增长补偿,然后更新sk_rcvbuf,上限为tcp_rmem[2]。只在应用读得快时放大窗口,且不会主动调小。

GBN和SR在丢包时的效率有什么不同?

GBN在窗口W≥K时效率为(1-p)/(1+(K-1)p),一次丢失会浪费K个时隙;SR理想效率为(1-p),但实际当W=K时因窗口阻塞效率与GBN相近,需W≥2K才能接近理想值。SR只在同一窗口内多次丢失时占优。

🏷️

标签

➡️

继续阅读