摘要

  • 愚蠢窗口综合征不是偶尔出现一个小报文,而是微小窗口增量不断复制出下一个小报文的稳定反馈循环。
  • TCP 让接收端暂缓细小窗口更新、让发送端暂缓低效发送,并用超时出口防止这种等待变成死锁。

先从接收应用的一次普通读取说起。缓冲区接近填满时,应用只取走了五十字节,于是 TCP 确实多出五十字节空间。接收端若马上通告这份空间,发送端就可以合法地发送五十字节。它们到达、被应用取走以后,窗口又增长五十字节。确认报文把这次增长带回发送端,下一段仍然只有五十字节。每一步都符合流量控制,却共同组成了一个越来越低效的节奏。

David Clark 在 1982 年的 RFC 813 中把这种节奏命名为 Silly Window Syndrome,即愚蠢窗口综合征(SWS)。关键不在于某个报文段偏小,而在于小尺寸形成了可以自我维持的模式。一次自然的数据边界可能把原本完整的可用窗口切开;此后的确认与窗口更新又按相同的碎片大小释放发送额度。只要长传输不中断,这些额度就缺少重新合并的自然机会。

RFC 813 为此区分了两个容易混为一谈的量。接收端通告的是“提供窗口”,表示它此刻愿意接收多少数据;发送端实际能使用的窗口,还要扣除已发出但尚未确认的数据。接收端即使通告一千字节,如果九百五十字节仍在途,发送端此刻也只能再发五十字节。等这五十字节被确认,窗口右缘再向前挪五十,发送端获得的依然是同样大小的机会。大窗口字段并没有带来大报文,决定结果的是窗口右缘每次移动的粒度。

这种退化远不只是多几个首部。RFC 813 记载,当时不良窗口算法可能让吞吐率和 CPU 利用效率出现数倍差距,还报告过平均报文段只有双方可处理尺寸十分之一、每个成功报文伴随多次重传的案例。这些数字是该文档记录的历史观察,不能直接外推为今天的部署状况;但它们解释了为什么 SWS 被当作传输机制问题,而不是无关紧要的调优偏好。

接收端的解法,是把“已有空闲内存”与“现在就要公布的接收额度”分开。应用每释放一小块空间,TCP 可以暂不向右移动所通告窗口的边界,把这部分空间留在本地账本里。等未通告的空间积累到足够大,再一次性开放。等得太久,网络中的流水线可能暂时排空,增加往返等待;开放得太勤,小报文循环却会同时消耗网络和主机处理资源。RFC 813 因而主张宁可稍微偏向克制,也应保证每次重新开放至少容纳一个相当大的报文段。

RFC 1122 把这项经验写成了主机要求。其第 4.2.3.3 节要求接收 TCP 必须包含 SWS 避免算法。建议规则保持 RCV.NXT + RCV.WND 不变,直到“已经可用但尚未通告”的空间达到两个阈值中较小的一个:接收缓冲区的一定比例,或一个有效发送 MSS。建议比例是二分之一。在现实的缓冲区关系下,这往往让窗口按接近一个报文段的单位增长,而不是按零碎字节增长。

只约束接收端仍然不够,因为一台主机不能假设对端一定实现得同样谨慎。RFC 1122 第 4.2.3.4 节也要求发送端拥有 SWS 避免算法。建议在以下任一条件满足时发送:可以发出一个最大尺寸报文段;在规定条件下可一次发完要求推送的排队数据;可以使用至少相当于此前所见最大窗口二分之一的额度;或者覆盖超时到期。于是,窗口给出的少量额度只是“允许发送”,并不自动等于“此刻发送最合理”。

发送端还面对一个接收端没有的信息缺口。接收端知道自己的总缓冲区,发送端却只能估计。RFC 1122 建议用连接期间见过的最大发送窗口近似接收容量,但接收端可能随后缩小缓冲区。如果发送端永远等待达到旧最大值的一定比例,聚合策略本身就可能造成死锁。因此算法必须保留覆盖超时。RFC 9293 延续了这条出口,并给出 0.1 至 1.0 秒的建议范围;这些一手资料并不能据此证明每个现代实现采用何种具体数值。

2022 年汇总现行 TCP 规范的 RFC 9293 保留了双边结构:发送端和接收端都必须实现 SWS 避免。它还明确区分了经常被混写的两种机制。Nagle 算法处理的是应用本身以小块方式提供数据时产生的小报文;发送端 SWS 避免处理的是接收窗口右缘以小步幅增长时产生的小报文。两者可以协作,却不针对同一个起因。