算法的故事 / 第一季第二季目录0 / 30 章已读

第二季 · 第 50 章

当整个互联网开始堵车

《算法的故事》第二季 · 第四篇:穿越噪声的信息——1986年的网络拥塞崩溃,范·雅各布森与TCP拥塞控制的诞生1986年10月,美国加利福尼亚州,伯克利。劳伦斯伯克利实验室的一台计算机,正试图通过网络向加州大学伯克利分校传输数据。两处地点相距并不遥远。按照研究人员后来发表的记录,它们之间只有约400码的距离。但在网络拓扑上,数据需要经过三个接口消息处理机(IMP)跳点。原本,网络连接能够达到大约:32 000 bit/s32\,000\text{ bit/s}然而,在那次严重拥塞期间,实际吞吐量下降到了:40 bit/s40\text{ bit/s}不是四千。也不是四百。只有每秒四十比特。吞吐量变成了原来的:4032000=1800\frac{40}{32000}=\frac1{800}也就是说,有效传输速度下降了800倍。对于使用网络完成计算与科研工作的人员来说,这几乎意味着网络失去了实用价值。令人困惑的是,问题并不能简单归结为线路突然断开。数据包仍然被发送。网络设备仍然处理流量。发送端还在不断重试那些没有收到确认的数据。但最终成功送达的有效数据,却少得惊人。这就是网络发展史上著名的一次:拥塞崩溃(Congestion Collapse)。20世纪80年代科研计算环境的历史影像。1986年的拥塞事件发生在劳伦斯伯克利实验室与加州大学伯克利分校之间的网络通信中。当时,两位研究者开始调查。一位是劳伦斯伯克利实验室的计算机网络研究人员:范·雅各布森(Van Jacobson)。另一位是加州大学伯克利分校计算机系统研究组的工程师:迈克尔·卡雷尔斯(Michael J. Karels)。他们关心的不只是眼前这条网络连接。他们想弄明白:为什么一项本来应该负责可靠传输数据的机制,反而可能把拥堵中的网络推向崩溃?如果网络已经拥堵,为什么发送端还会继续发送越来越多的重传包?能不能让每台计算机在不需要全网统一指挥的情况下,根据网络反馈自行调整发送速度?两年之后,1988年8月,雅各布森发表了一篇影响深远的论文:Congestion Avoidance and Control《拥塞避免与控制》。论文刊登于ACM SIGCOMM 1988会议论文集中。第一页,作者便记录了1986年10月从32 Kbit/s下降到40 bit/s的真实事件。论文还列出了研究者为4BSD TCP开发的七项改进,包括:慢启动。往返时间变化估计。重传超时的指数退避。以及根据拥塞动态调整发送窗口。 [cs162.eecs.berkeley.edu]1988年论文《Congestion Avoidance and Control》。原始论文署名作者是Van Jacobson,第一页脚注明确感谢并说明核心算法与Mike Karels合作开发。[cs162.org]在这些机制之中,有一项看起来特别简单的思想。没有发现拥塞,就谨慎增加发送量。一旦出现拥塞信号,就明显减少发送量。这项设计后来成为经典TCP拥塞控制的重要基础。它让我们看到:一张共享网络,并不只是由电缆、交换设备和路由器构成。它还需要一套协调各个发送端行为的反馈机制。因为即使所有计算机都能够正确传输数据,也不意味着整个网络就能高效工作。如果每台计算机都拼命争夺有限资源,它们就可能共同制造一个没有人受益的结果。这一章,我们要研究的正是这个问题:为什么一条越忙碌的网络,反而可能传输得越慢?

第一幕:先认识一项反直觉的事实——丢包并不是拥塞崩溃本身

要理解1986年的事件,我们需要先认识:分组交换(Packet Switching)。互联网传输一份文件时,通常不会像运送一件无法拆开的巨型货物那样,将全部数据一次性占用整条通道。它会把数据划分成许多适合网络传输的分组。我们通常称它们为:数据包(Packet)。这些数据包经过网络设备转发,最后到达接收端。在TCP通信中,接收端还会返回确认信息:ACK(Acknowledgment)。发送端利用这些确认信息,判断哪些数据已经被接收,并在必要时进行重传。

路由器为什么会出现排队?

想象一台网络设备。它同时收到三台计算机发送的数据。但下一条输出链路的传输能力有限。假设三台计算机合计每秒送来:12 MB12\text{ MB}而输出链路每秒只能传输:8 MB8\text{ MB}那么,每秒就有:12−8=4 MB12-8=4\text{ MB}的数据无法立即发走。如果网络设备有足够大的缓冲区,这些数据可以暂时排队。但缓冲区不是无限的。如果这种超量输入持续存在,队列就会越来越长。最后,缓冲区被填满。新的数据包可能被丢弃。

用一个公式表示排队

令:QtQ_t表示第 tt 个时间间隔开始时,队列中等待处理的数据量。令:AtA_t表示该时间间隔进入队列的数据量。令:CtC_t表示该时间间隔可提供的服务量。忽略有限缓冲和其他复杂因素时,可以写成:Qt+1=max⁡(0,Qt+At−Ct)\boxed{ Q_{t+1}=\max(0,Q_t+A_t-C_t) }如果持续满足:At>CtA_t>C_t队列就会增长。若设备还存在容量上限:BB那么超过缓冲能力的数据,便可能被丢弃。
图50-1:数据到达速度超过链路服务能力
发送端1发送端2发送端3有限缓冲队列瓶颈链路到达 12 MB/s,服务 8 MB/s
查看 SVG 配图原件
示意图原件 50-005-433dec1add0f.svg
50-005-433dec1add0f.svg · 点击查看大图
教学示意。真实网络中的数据流、服务速率和丢包行为,可能随时间、协议和队列管理策略变化。
到这里,拥塞问题似乎已经清楚了。数据太多。处理不过来。因此,发生排队或丢包。但问题还没有结束。因为TCP不是发送一次就不管结果的机制。

丢包以后,发送端会尝试重传

假设发送端传输一个数据包。由于缓冲区溢出,网络设备把它丢弃了。发送端迟迟没有收到相应确认。于是,它认为数据可能没有成功送达。便重新发送。如果此时网络已经非常拥堵,那么新发出的重传包,也会与其他数据包竞争同一份带宽和缓冲资源。更严重的是:如果重传定时器不准确,它可能把一个只是被排队耽搁、尚未丢失的包误判为丢失。于是,原始数据包还在路上,重复副本又被发进网络。这会浪费更多资源。而更多的无效重传,又可能导致更严重的排队和丢包。于是,一个正反馈循环出现了。
图50-2:拥塞崩溃可能形成的反馈循环
数据过量进入网络
排队延迟增加,部分数据丢失
超时或丢失触发更多重传
更多带宽用于重复数据而非新数据
反过来加重拥塞
这才是“拥塞崩溃”真正令人担忧的地方。不是网络偶尔丢了几个数据包。而是网络中的大量工作,没有转化为真正成功交付的新数据。数据包被重复发送。队列被反复填满。网络设备消耗处理能力。但接收端最终获得的有效信息却越来越少。在网络性能研究中,人们通常将这种真正成功交付的有效新数据吞吐量称为:Goodput(有效吞吐量)。它不等于链路上所有流量的总和。

为什么每秒32千比特会跌到40比特?

雅各布森在1988年的论文里,并没有将这一现象简单归因于一条坏掉的电缆。他与Karels研究了4.3BSD TCP在严重拥塞条件下的行为。并发现:发送窗口管理、启动时的突发数据、往返时间估计与重传超时等多个方面,都需要改善。[cs162.eecs.berkeley.edu]因此,真正需要解决的,不只是让每台计算机更快地重发丢失的数据。而是让整个网络中的发送行为,能够根据实际反馈自动调整。这引出了本章第一个重要思想。可靠传输必须与拥塞控制协同工作;否则,局部的可靠性努力可能伤害全局的传输效率。

第二幕:1984年,约翰·内格尔已经为互联网敲响警钟

如果我们只讲1986年的崩溃,很容易产生一种误解。似乎在1986年之前,全世界没有人意识到网络拥塞可能造成严重后果。但真实历史并不是这样。早在1984年1月6日,工程师:约翰·内格尔(John Nagle)就发表了:RFC 896 — Congestion Control in IP/TCP Internetworks《IP/TCP互联网中的拥塞控制》。他在这份文件中,已经明确讨论了:拥塞崩溃。TCP与IP层之间的相互影响。以及不同链路速度连接在一起时,可能产生的拥塞问题。[RFC Editor]

这说明什么?

说明1986年的事件,并不是互联网历史上第一次有人提出拥塞崩溃的概念。更准确的说法是:1986年10月发生的严重事件,成为雅各布森与Karels研究和改进TCP的一项直接推动力。而关于拥塞控制的讨论,至少在1984年的RFC中已经十分明确。

为什么网络会越来越脆弱?

早期的计算机网络规模相对较小。许多连接也处于研究机构内部。但随着不同网络相互连接,一项新的问题出现了。局部网络之间的速度可能相差很大。例如:一端使用速度较快的以太网。另一端却通过速度相对较低的长距离链路连接。如果快速网络里的计算机,按照本地链路能力一口气发出大量数据,那么低速瓶颈前面的缓冲队列就可能迅速积累。雅各布森在1988年论文中举过类似例子:10 Mbit/s的以太网主机,通过IP网关连接56 Kbit/s的ARPANET链路。如果发送端一下子发出较大的窗口,就可能在瓶颈处形成极其密集的数据突发。[cs162.eecs.berkeley.edu]

1984年的方案与1988年的突破,有什么不同?

内格尔已经指出了拥塞问题,也讨论过一些控制思路。后来的雅各布森与Karels研究,则围绕4BSD TCP的具体行为,发展出一套相互配合的发送端机制。它们涉及:从小窗口逐渐建立传输。利用确认信息调节后续发送。根据拥塞信号减小窗口。改善重传定时器。以及减少无意义的重传。所以,历史并不是:某个人从零开始发明所有拥塞控制技术。而是:早期研究者识别出问题。实际网络出现严重性能崩溃。多个研究团队提出控制原则。随后,一系列算法与工程实现逐步形成更加可靠的TCP行为。

1988年论文中的一个容易忽略的署名细节

论文首页的正式署名是:Van Jacobson。但第一页脚注中特别强调,这些算法和思想是与Mike Karels合作开发的。因此,准确的历史表述应是:雅各布森是1988年论文的署名作者,Karels是相关算法开发的重要合作者。直接把两人都写成论文首页的正式署名作者,并不符合该版本原文。类似地,论文也明确将其中一项与重传定时器有关的机制归于Phil Karn先前的研究。这再次说明,TCP拥塞控制的成熟并非来自单一人物的全部独立发明。

第三幕:一项经常被混淆的区别——流量控制不等于拥塞控制

在讲述雅各布森的算法之前,我们需要先理解两个窗口。这是TCP拥塞控制中最容易混淆、也最重要的概念之一。接收窗口(Receive Window,rwnd)。以及:拥塞窗口(Congestion Window,cwnd)。二者看起来都在限制发送量。但它们实际上回答完全不同的问题。

接收窗口:接收端还能接收多少?

假设计算机A向计算机B发送文件。计算机B需要把收到的数据暂时保存在接收缓冲区里。如果B的应用程序处理数据很慢,那么这个缓冲区就可能逐渐填满。此时,B会通过TCP窗口通告,告诉A:目前还有多少接收空间可用?这就是:rwndrwnd它解决的是:发送端不能把接收端淹没。例如,B通告:rwnd=8 KBrwnd=8\text{ KB}那么,在不考虑其他协议细节时,发送端不能无视该限制,向B持续发送大量尚未确认的数据。这是端到端的流量控制。

拥塞窗口:整个网络现在能承受多少?

但是,即使B的接收能力非常强,也不代表中间网络具有同样大的传输能力。假设:A拥有很高的发送速度。B拥有很大的接收缓冲区。但它们之间经过一条低速链路。那么,真正的限制来自网络内部。不是B来不及读取数据。而是中间链路和队列承受不了发送端注入的数据量。这时候,就需要:cwndcwnd拥塞窗口是发送端维护的一个状态变量。它根据网络反馈,限制当前允许有多少尚未确认的数据处于传输过程中。这是拥塞控制。

真正允许发送多少?

在基本TCP发送控制中,允许在途的数据量同时受到:rwndrwnd和:cwndcwnd限制。因此:Weffective=min⁡(rwnd,cwnd)\boxed{ W_{\mathrm{effective}}=\min(rwnd,cwnd) }其中,实际还能发送的新数据,还需要减去已经发送但尚未得到确认的在途数据量。例如:rwnd=64 KBrwnd=64\text{ KB}cwnd=16 KBcwnd=16\text{ KB}则:Weffective=16 KBW_{\mathrm{effective}}=16\text{ KB}此时主要由拥塞窗口限制。如果反过来:rwnd=8 KBrwnd=8\text{ KB}cwnd=64 KBcwnd=64\text{ KB}则:Weffective=8 KBW_{\mathrm{effective}}=8\text{ KB}此时主要由接收窗口限制。
图50-3:两个窗口,解决两种不同的限制
接收窗口 rwnd接收端缓冲与处理能力的限制。
拥塞窗口 cwnd发送端根据网络拥塞反馈维护的限制。
Weffective=min⁡(rwnd,cwnd)W_{\mathrm{effective}}=\min(rwnd,cwnd)这是用于解释在途数据上限的基本关系;实际TCP还包含发送缓冲、已确认序号、窗口更新及其他实现细节。

为什么这个区别非常重要?

因为接收端可以拥有足够的能力。但网络本身仍然可能拥堵。所以,接收窗口不能代替拥塞窗口。同样,网络可能非常空闲。但接收端缓冲区已经满了。这时,仅仅增大拥塞窗口,也不能解决接收端处理不过来的问题。因此,流量控制与拥塞控制必须同时存在。RFC 5681明确将这两种窗口区分开,并规定发送端同时受到二者约束。[RFC Editor]

1986年的问题,究竟发生在哪里?

雅各布森与Karels关注的是:即使TCP已经具有一定的端到端可靠传输和流量控制机制,网络内部的共享资源仍然可能被过量发送与重传压垮。所以,仅仅知道接收端能够接收多少数据,远远不够。发送端还需要估计:这条经过多个网络设备的通信路径,目前究竟能够稳定容纳多少在途数据?而这个问题,将我们带到雅各布森论文中的另一项关键概念。

第四幕:1988年,一位网络研究者把数据包当成了流动的水

在1988年的论文里,雅各布森提出了一项非常重要的观察。一个已经稳定运行的TCP连接,应该遵守某种:数据包守恒原则(Conservation of Packets)。他的意思并不是:互联网中的数据包数量永远不会变化。因为应用程序会产生新数据。网络设备也可能丢弃旧数据。他讨论的是一种已经达到平衡、拥有持续数据流的连接。在这种条件下:一个旧的数据包离开网络,发送端才应该让另一个新数据包进入网络。这就像一根充满水的管道。如果从管道的一端持续注入水,那么流出速度必须与长期输入速度相匹配,系统才能保持稳定。如果输入速度长期超过输出速度,水就会在入口附近积累。对于网络而言,积累发生在缓冲队列里。

为什么ACK可以作为一种时钟?

当数据包成功抵达接收端之后,接收端会产生确认信息。ACK沿返回路径送回发送端。因此,ACK的到达,往往代表之前发送的部分数据已经通过网络,并释放了对应的在途数据额度。发送端可以利用这些确认信息,安排新的数据发送。这种机制叫作:ACK Clocking(确认时钟)。或:Self-Clocking(自时钟)。雅各布森1988年论文第一部分专门解释了这个机制。其核心思想是:发送端不需要按照本地高速网卡的最大速度,不间断地向网络注入数据。它可以利用数据经过瓶颈后形成的ACK反馈节奏,调节后续数据的释放。[cs162.eecs.berkeley.edu]

什么叫作瓶颈链路?

想象从A到B的路径包含三条链路:第一条:100 Mbit/s100\text{ Mbit/s}第二条:10 Mbit/s10\text{ Mbit/s}第三条:100 Mbit/s100\text{ Mbit/s}如果没有其他限制,整条路径的长期传输能力,通常受到中间那条:10 Mbit/s10\text{ Mbit/s}链路的限制。这条最慢的关键链路,便是当前路径的瓶颈。
图50-4:一条速度不均匀的网络路径发送端网关网关接收端100 Mbps10 Mbps100 Mbps瓶颈链路持续过量输入会在瓶颈附近形成排队

网络到底需要容纳多少在途数据?

这时,我们要引入一个很重要的概念:带宽时延积(Bandwidth–Delay Product,BDP)。它的定义是:BDP=B×RTT\boxed{BDP=B\times RTT}其中:BB表示瓶颈带宽。RTTRTT表示往返时延。这个公式的单位很有意思。带宽单位:bit/s\text{bit/s}时延单位:s\text{s}相乘得到:bit\text{bit}因此,BDP可以表示,在理想稳定条件下,为了充分利用一条给定带宽与往返时延的网络路径,需要容纳多少在途数据。

一个具体的例子

假设瓶颈带宽:B=10 Mbit/sB=10\text{ Mbit/s}往返时延:RTT=0.1 sRTT=0.1\text{ s}那么:BDP=10 000 000×0.1BDP=10\,000\,000\times0.1得到:BDP=1 000 000 bit\boxed{BDP=1\,000\,000\text{ bit}}也就是:125 000 bytes125\,000\text{ bytes}约为125 KB。如果每个数据段承载1000字节的有效数据,那么:1250001000=125\frac{125000}{1000}=125因此,在这个简化模型中,要让瓶颈链路持续保持繁忙,发送端可能需要允许大约125个这样的数据段处于在途状态。如果拥塞窗口只有:10 个数据段10\text{ 个数据段}那么,即使网络并不拥堵,也可能无法充分利用带宽。如果拥塞窗口突然达到:1000 个数据段1000\text{ 个数据段}又可能形成不必要的大队列,甚至导致丢包。

发送端遇到的难题

现在,我们已经看到了真正的问题。拥塞窗口不能太小。否则浪费可用带宽。但也不能毫无节制地变大。否则造成严重排队与丢包。更困难的是:网络路径随时可能变化。新的用户开始传输数据。旧的连接结束。不同链路的可用带宽发生变化。某台路由器开始拥堵。因此,发送端不能简单地将拥塞窗口固定成一个永远正确的常数。它需要根据网络反馈,持续调整。那么,一台刚刚开始传输数据的计算机,究竟应该把初始拥塞窗口设为多少?雅各布森的答案,出乎许多人意料。先从很小开始。但增长速度,不必一直很慢。

第五幕:1987年,一个名字叫“慢启动”的算法,为什么增长得这么快?

1987年前后,雅各布森与Karels开始采用一种新的连接启动机制。它后来被称作:Slow Start(慢启动)。这个名字非常容易让初学者误解。慢启动是不是意味着:拥塞窗口每过一秒,只增加一点点?并不是。事实上,在经典简化模型下,慢启动的窗口增长是:近似指数增长。

为什么起点要小?

假设一台计算机刚刚建立TCP连接。它并不知道网络路径现在可以容纳多少数据。如果它一开始就允许大量未确认数据同时进入网络,就可能制造一个非常大的突发流量。对于一条存在低速瓶颈的路径,这种突发可能直接填满中间队列。因此,经典的早期慢启动方法,是从一个很小的拥塞窗口开始。在1988年论文中,雅各布森明确描述了:连接开始或发生丢包后重新启动时,将拥塞窗口设置为一个数据包。然后,收到新的数据确认,就将窗口增加一个数据包。[cs162.eecs.berkeley.edu]但注意:1988年的初始窗口参数,并不是所有现代TCP都必须沿用的初始窗口大小。现代TCP的初始窗口可以更大。例如,RFC 6928在2013年提出允许初始窗口达到10个报文段的实验性建议。不同操作系统、协议实现及网络条件可能采用不同策略。本章先使用1988年的经典规则,帮助理解算法思想。

第一轮:只允许发送一个包

假设:cwnd=1cwnd=1发送端发送一个数据包。当收到这个包的确认时,拥塞窗口增加一个数据包。于是:cwnd=2cwnd=2

第二轮:允许两个包在途

现在,窗口是:22如果发送两个新的数据包,并分别收到它们的确认,那么每个ACK都能使拥塞窗口增加一个包。所以:2→3→42\rightarrow3\rightarrow4经历大约一个往返时延以后:cwnd≈4cwnd\approx4

第三轮:从四增加到八

同样道理:4→84\rightarrow8再过一轮:8→168\rightarrow16于是,慢启动阶段的典型变化是:1, 2, 4, 8, 16, 32,…\boxed{1,\ 2,\ 4,\ 8,\ 16,\ 32,\ldots}这不是线性增长。而是近似每个RTT翻倍。
图50-5:慢启动为何呈现指数增长
RTT轮次窗口,单位MSS约相当于上一轮
01—
12×2
24×2
38×2
416×2
532×2
假设发送端始终有数据、接收端不限制窗口、每段都产生有效确认,且没有丢包。表格按RTT聚合显示,不代表实际TCP只会在RTT结束时整批更新窗口。

为什么“慢启动”这个名字反而指指数增长?

因为它是相对于:一开始就发送大量数据而言的。它并不是与后面的拥塞避免阶段比较增长速度。慢启动从小窗口开始,利用ACK逐渐建立发送节奏。这样,网络能够通过确认反馈,影响后续新数据的注入。而不是让发送端仅按照本地网卡速度,不断向网络倾倒数据。

这个名字是谁提出的?

还有一项非常有趣的历史细节。雅各布森在1988年论文的脚注中明确记载:1987年2月向IETF介绍这套方法时,他们曾经使用:Soft Start(软启动)。而:Slow Start(慢启动)这个名称,是John Nagle于1987年3月在IETF邮件列表中建议的。雅各布森认为,新名字更加合适,于是采纳。[cs162.eecs.berkeley.edu]因此,不能把“Slow Start”这个名字的发明,也简单地全部归给雅各布森本人。

还有一个更重要的历史承认

原论文同时指出,慢启动与Raj Jain早先介绍的一种:CUTE算法十分相似。作者表示,他们发展慢启动时并不知道那项先前研究,但CUTE在时间上更早几个月。这说明:在互联网拥塞控制发展史上,研究者们曾从不同路线提出相似的机制。承认这些先后关系,并不会削弱雅各布森与Karels的贡献。反而能让我们更准确地理解这项技术是怎样逐渐成熟的。

那么,慢启动会不会无限翻倍?

当然不能。假设:cwnd=1,2,4,8,16,32,64,…cwnd=1,2,4,8,16,32,64,\ldots如果一直指数增长,迟早会超过网络能够容纳的资源。于是,TCP还需要一个新的变量:慢启动阈值(Slow Start Threshold,ssthresh)。它帮助发送端判断:什么时候应该结束快速增长?什么时候应该进入更加谨慎的增长阶段?这便是下一幕的主角。

第六幕:慢启动之后,为什么必须从“翻倍”改成“每轮加一”?

现在,假设一条TCP连接已经完成了最初的启动。拥塞窗口从:11逐渐增加到:1616网络暂时没有出现明显的拥塞信号。但这是否意味着,我们应该继续:16→32→64→12816\rightarrow32\rightarrow64\rightarrow128这样快速增长?如果路径当前的可用容量只相当于20个数据段,那么这种做法就可能造成严重的超量发送。因此,发送端需要在适当时候改变策略。从快速探测,转向更加谨慎的探测。这项机制叫作:拥塞避免(Congestion Avoidance)。

慢启动阈值 ssthresh

在经典TCP拥塞控制中,维护一个变量:ssthreshssthresh即慢启动阈值。如果:cwnd<ssthreshcwnd<ssthresh通常使用慢启动增长。如果:cwnd>ssthreshcwnd>ssthresh使用拥塞避免增长。在两者相等的边界上,具体规则可以由相应TCP规范确定。RFC 5681允许在相等时采用其中一种方式。[RFC Editor]为了让实验逻辑明确,本章的教学模型约定:cwnd<ssthreshcwnd<ssthresh时采用慢启动。否则采用拥塞避免。

慢启动:每个RTT近似翻倍

如果当前窗口为:WW那么在理想、充分确认的情况下:Wnext≈2W\boxed{W_{\mathrm{next}}\approx2W}

拥塞避免:每个RTT近似增加一个MSS

一旦进入拥塞避免,算法不再快速翻倍。而是改为:Wnext≈W+1\boxed{W_{\mathrm{next}}\approx W+1}这里将窗口单位简化为一个最大报文段大小:MSS(Maximum Segment Size)。因此,如果:cwnd=16cwnd=16那么后续大致是:16→17→18→19→2016\rightarrow17\rightarrow18\rightarrow19\rightarrow20而不是:16→32→6416\rightarrow32\rightarrow64

但是,实际算法不是每秒简单加一

在1988年的原始论文中,雅各布森给出了一项非常简洁的规则。假设拥塞窗口使用“数据包数”作为单位。每收到一项确认新数据的ACK,就将窗口增加:Δcwnd=1cwnd\boxed{\Delta cwnd=\frac1{cwnd}}为什么是:1cwnd\frac1{cwnd}而不是每收到一个ACK就加1?因为当:cwnd=Wcwnd=W时,在一个RTT里大约可以得到 WW 次对应的确认。因此,整个RTT累计增加:W×1W=1W\times\frac1W=1于是,每个RTT大约增加一个数据包大小的窗口额度。对于以字节作为窗口单位的TCP,常见的相应增量形式为:Δcwnd≈SMSS2cwnd\boxed{ \Delta cwnd\approx\frac{SMSS^2}{cwnd} }其中:SMSSSMSS表示发送端最大段大小。实际规范和实现可能采用按确认字节计数等更精细的方法,以处理延迟ACK、整数舍入等问题。[cs162.eecs.berkeley.edu][RFC Editor]

为什么这项设计如此重要?

因为网络中的资源不是固定不变的。假设我们刚才探测到:当前路径大约能够支撑20个在途数据段。但是,过了一会儿,另一个用户结束了大文件传输。可用带宽增加了。如果我们的窗口永远停留在20,就可能浪费新增的网络资源。因此,发送端不能一旦稳定就停止探测。它必须继续谨慎增加发送量,寻找网络当前能够提供的能力。但增加速度又不能过快。于是:每个RTT大约增加一个MSS成为一种经典的谨慎探测策略。

如果拥塞出现,应该怎么做?

这时,仅仅停止增加窗口还不够。因为当前已经出现的拥塞,说明发送端可能正在占用过多的网络资源。因此,算法需要主动退让。在经典的加性增加、乘性减小策略中:Wnew=βWold\boxed{W_{\mathrm{new}}=\beta W_{\mathrm{old}}}其中:0<β<10<\beta<1常见的经典选择是:β=12\beta=\frac12因此:Wnew=W2\boxed{W_{\mathrm{new}}=\frac W2}如果窗口原来是:2020拥塞发生后便可以减到:1010然后再从10开始,谨慎向上增加。这形成了一种经典的锯齿形窗口变化。

这就是AIMD

AIMD是:Additive Increase, Multiplicative Decrease。中文:加性增加、乘性减小。其数学形式可以写成:Wt+1={Wt+α,未发现拥塞βWt,发现拥塞\boxed{ W_{t+1}= \begin{cases} W_t+\alpha,&\text{未发现拥塞}\\ \beta W_t,&\text{发现拥塞} \end{cases} }其中:α>0\alpha>0通常取每RTT约1 MSS。而:0<β<10<\beta<1经典减小因子为:β=0.5\beta=0.5
图50-6:经典AIMD形成的锯齿形窗口
拥塞拥塞时间(RTT)cwnd
查看 SVG 配图原件
示意图原件 50-011-c180cc1809bc.svg
50-011-c180cc1809bc.svg · 点击查看大图
这是经典AIMD的概念性锯齿图,并非1986年实际网络采样数据。真实TCP遇到超时、重复ACK、ECN标记或其他事件时,窗口行为可能不同。

为什么增加要慢,减小要快?

因为它们面临的风险不同。如果网络还没有拥塞,我们并不知道究竟还有多少剩余资源。所以可以一点点增加,谨慎探测。但如果已经有了拥塞信号,说明网络资源可能已经不够。此时,继续大幅增加只会进一步加重问题。因此,需要较明显地减少发送压力。这种非对称设计,与很多反馈控制系统的思想相似。不过,不能把它理解成:永远只要看到一个丢包,就一定准确判断网络拥塞。真实网络也可能因为无线误码、路由变化或其他原因发生丢包。经典TCP主要使用丢包作为拥塞信号。后来的网络技术还发展了显式拥塞通知(ECN),以及其他利用时延或带宽估计的拥塞控制方法。

AIMD也不是雅各布森独自第一次提出的数学思想

1988年的原始论文明确承认,类似的加性增加、乘性减小策略,已经由Raj Jain等研究者在DEC/ISO网络拥塞避免研究中提出。雅各布森写道,他的TCP实现采用了类似方案,并选择:α=1,β=0.5\alpha=1,\qquad \beta=0.5等具体参数。[cs162.eecs.berkeley.edu]1989年,Dah-Ming Chiu与Raj Jain又发表了关于增减策略的系统分析。论文研究了:效率。公平性。收敛性。以及振荡幅度。并在明确的模型假设下,解释了为什么AIMD具有良好的效率与公平性收敛性质。[cs.wustl.edu]因此,历史应该这样理解:雅各布森与Karels的重要贡献,是将一组拥塞控制思想有效整合进TCP实现,改善真实互联网在严重拥塞下的行为;AIMD的理论发展还包含Jain、Chiu等研究者的重要工作。

第七幕:为什么一次丢包,可能需要两种不同的后退方式?

前面我们说:发生拥塞后,窗口可以减半。但真实TCP还有一个非常重要的问题。发送端究竟怎样知道丢包发生了?在经典TCP中,至少有两种具有代表性的情况。第一种:等待确认太久,发生重传超时。第二种:连续收到多个重复ACK,推断某个数据段可能丢失。这两种信号透露的信息不同。因此,后来的TCP拥塞控制也会采取不同的恢复方式。

情况一:重传超时

假设发送端发出了很多数据。随后,长时间没有收到能够确认相关新数据的反馈。重传定时器最终到期。这时,发送端可能无法确定:究竟有多少数据还在网络里?网络是否严重拥堵?原先建立的ACK节奏是否仍然有效?因此,经典的保守策略是:大幅减小拥塞窗口。并重新进入慢启动。在RFC 5681描述的经典算法里,超时后的拥塞窗口被重置为一个完整大小的报文段。同时,将慢启动阈值设置为与此前在途数据量有关的较小值,常见地约为其一半,并受到至少两个SMSS的下限约束。[RFC Editor]为了教学,我们可以简化成:ssthresh←max⁡(2,W2)ssthresh\leftarrow\max(2,\frac W2)cwnd←1\boxed{cwnd\leftarrow1}这里的窗口单位是MSS。请注意:这是超时恢复阶段的一种简化表示。它与普通拥塞避免中的“仅把窗口减半”,不是完全相同的动作。

情况二:连续收到重复ACK

现在,假设发送端依次发送:数据段1。数据段2。数据段3。数据段4。数据段5。但数据段2丢失。后面的数据段3、4、5却陆续到达接收端。接收端发现:自己仍然缺少数据段2。因此,可能继续发送确认,表示:目前仍然在等待从数据段2开始缺失的数据。发送端连续收到多个相同确认号的ACK。这就是:重复ACK(Duplicate ACK)。
图50-7:数据段2丢失后,后续到达的数据可能产生重复ACK
发送端接收端段1段2段3段4段5后续到达可能促使接收端再次确认缺口
查看 SVG 配图原件
示意图原件 50-013-031b011d289d.svg
50-013-031b011d289d.svg · 点击查看大图
示意图省略了具体ACK返回时序、TCP序号和接收缓冲细节。重复ACK并不等于接收端发出了一个特殊的“段2已经丢失”控制包。

三个重复ACK,能够提供什么信息?

在经典TCP快速重传规则中:如果连续收到三个满足条件的重复ACK,发送端可以把它作为某个数据段可能丢失的信号。于是,不必等到重传定时器到期,就可以提前重传疑似丢失的数据。这叫作:Fast Retransmit(快速重传)。为什么三个重复ACK如此有价值?因为它们不仅提示某个数据可能缺失。还说明:后面的其他数据段仍然可能成功穿过网络,抵达接收端。因此,网络并不是完全停止传输。ACK反馈节奏仍然可能部分存在。这为采用比完整超时重启更温和的恢复方式,提供了依据。

快速恢复

后来经典TCP Reno发展了:Fast Recovery(快速恢复)。在收到适当的重复ACK信号后,发送端可以在减小拥塞窗口的同时,尽量避免像严重超时那样完全重新从最小窗口开始。这能够在某些丢包情形下保持更好的传输效率。不过,历史上必须区分:1988年论文已经讨论了快速重传,并列出一系列4BSD TCP改进。但后来广泛熟悉的完整TCP Reno快速恢复机制,涉及后续的算法演进与规范整理。不能将2009年RFC 5681中的全部具体规则,都毫无区别地说成雅各布森在1986年一次性完成的设计。

一个比较表

图50-8:经典TCP对不同丢失信号的反应
事件说明经典控制思路
无拥塞信号持续收到新数据确认慢启动或拥塞避免增长
重传超时长时间没有获得所需确认减小阈值,重新慢启动
三次重复ACK暗示存在数据缺口,但后续数据仍可能到达快速重传,经典Reno使用快速恢复
这里还必须强调:真实TCP的重复ACK处理包含更严格的条件。网络乱序也可能产生重复ACK。现代实现还可结合选择确认(SACK)及其他丢失检测机制。因此,三次重复ACK是一项经典启发式丢失信号,而不是数学上绝对无误的丢包证明。

第八幕:重传定时器为什么不能只看平均延迟?

在1988年的论文中,雅各布森并不只是研究拥塞窗口。他还投入了大量精力,研究另一项看起来更加基础的问题:TCP到底应该等待多久,才判断某个数据包可能需要重传?这个时间叫作:重传超时(Retransmission Timeout,RTO)。

假设平均往返时间是100毫秒

如果我们观察到一条网络连接,平均RTT大约:100 ms100\text{ ms}那么,直接设置:RTO=200 msRTO=200\text{ ms}似乎很合理。但假设网络排队时间突然增加。某个数据包的RTT变成:280 ms280\text{ ms}这个数据包其实没有丢失。只是排队时间更长。可是发送端在200毫秒时,就已经重新传输了一份副本。于是,无意义的重传增加了网络负担。如果这种误判频繁发生,就可能形成严重的正反馈问题。

为什么只看平均RTT不够?

因为网络延迟并不是固定常数。在轻载情况下:RTT可能很稳定。在拥塞情况下:数据包可能经历很长、变化很大的排队时间。因此,我们不仅需要估计平均RTT。还需要估计:RTT的变化程度。雅各布森1988年的论文专门讨论了往返时间方差估计,并提出基于平均值和变化量调整重传定时器的算法。[cs162.eecs.berkeley.edu]这项思想后来进入TCP规范。

现代规范中的两个状态变量

RFC 6298规定了计算TCP重传定时器的方法。其中维护:SRTTSRTT即平滑后的往返时间估计。以及:RTTVARRTTVAR即往返时间变化程度的平滑估计。其基本形式为:RTO=SRTT+max⁡(G,4RTTVAR)\boxed{ RTO=SRTT+\max(G,4RTTVAR) }其中:GG是计时器粒度。之后还需要应用规范中的最小RTO约束与其他规则。[RFC Editor]

一个具体例子

假设:SRTT=600 msSRTT=600\text{ ms}RTTVAR=200 msRTTVAR=200\text{ ms}计时器粒度足够小。那么:RTO=600+4×200RTO=600+4\times200得到:RTO=1400 ms\boxed{RTO=1400\text{ ms}}也就是:1.4 s1.4\text{ s}这个定时器不仅考虑平均往返时间。还为延迟变化保留了一定余量。当然,现实实现中的RTO会随着新样本继续更新。

如果真的超时了呢?

假设发生一次重传超时。发送端重新发送数据。但后续再次超时。如果每次都使用同样短的等待时间,发送端可能继续向已经拥塞的网络快速注入重复数据。因此,TCP采用:指数退避(Exponential Backoff)。即在连续超时的情况下,增加下一次重传等待时间。典型地,可以理解为:RTO,2RTO,4RTO,8RTO,…RTO,\quad2RTO,\quad4RTO,\quad8RTO,\ldots当然,实际TCP实现还受到最小值、最大值以及其他计时器规则约束。这项机制让发送端在严重网络异常时,逐渐放慢重试速度。

还有一个隐藏问题:重传后的ACK到底确认了哪份数据?

假设某个数据包第一次发出。迟迟没有得到确认。于是,发送端重传。随后收到ACK。这个ACK究竟是对第一次发送的确认?还是对第二次重传的确认?如果没有额外信息,发送端可能无法知道。因此,若直接根据这个ACK计算RTT,就可能得到有歧义的结果。这便是所谓的:重传歧义(Retransmission Ambiguity)。研究者Phil Karn等人在1980年代提出了相关的RTT估计处理规则。常见做法是避免从存在重传歧义的数据段中计算新的RTT样本,同时正确保留重传定时器的退避行为。1988年雅各布森论文明确将这项相关机制归于Karn的先前工作。现代RFC 6298也规定,除非使用合适的机制消除歧义,否则不应对重传段采用有歧义的RTT测量。[cs162.eecs.berkeley.edu][RFC Editor]

这揭示了拥塞控制中一个深刻的原则

发送端不只是需要知道:有没有丢包?还需要估计:网络目前有多慢?延迟变化有多大?过去的网络状态是否仍然具有参考价值?何时重试才不会进一步伤害网络?因此,真正有效的拥塞控制,并不是单独的一条窗口更新公式。它还依赖:反馈测量。计时器设计。丢失检测。以及发送节奏。这一点正是1988年论文的重要价值所在。

第九幕:互动实验室——亲眼看见TCP窗口从指数增长变成锯齿

前面几幕,我们已经掌握了TCP经典拥塞控制的三项核心动作。第一项:连接刚开始时,使用慢启动。第二项:达到阈值后,使用拥塞避免。第三项:发现拥塞后,减少窗口,并根据丢失信号选择恢复方式。现在,我们将它们放进同一个模拟实验。

实验模型

假设我们拥有一条简化的网络路径。为了聚焦拥塞窗口算法,实验不逐个模拟实际的数据包、ACK和路由器队列。而是用一个参数:CC表示当前路径能够在不触发拥塞事件的情况下容纳的窗口阈值。单位为MSS。每个模拟轮次代表一个理想化的RTT。如果窗口:cwnd≤Ccwnd\le C就认为该轮数据顺利获得确认。如果窗口:cwnd>Ccwnd>C就触发一次拥塞事件。这项规则是为了教学设计的简化反馈模型。它并不意味着真实TCP能直接观察到一个固定的、完全准确的网络容量数字。

可调节的TCP拥塞窗口实验

图50-9:TCP拥塞窗口动态实验调节网络容纳阈值、初始慢启动阈值和拥塞后的反应,观察窗口如何持续探测可用容量。

20 MSS

12 MSS

RTT轮次
16 / 30
当前cwnd

4

MSS
当前ssthresh

10.5

MSS
累计拥塞事件

1

次
本轮执行的操作
慢启动:窗口快速增长
本轮窗口4 MSS
下轮窗口

8 MSS

此实验是按RTT聚合的教学模型:把超过预设窗口阈值简化为拥塞反馈。它没有实现TCP序号、ACK细节、SACK、真实RTO、排队动力学或完整Reno快速恢复,因此曲线不能当作某个真实操作系统的精确网络抓包记录。

实验一:为什么慢启动一开始增长很快?

恢复默认参数。观察前几轮。你会发现,窗口从1开始。在低于慢启动阈值时,按照近似指数规则增长。而当:cwndcwnd达到阈值后,进入拥塞避免。增长速度明显变慢。

实验二:为什么网络容量越小,拥塞越容易出现?

选择:“低容量路径”。假设网络只允许相当于10 MSS的窗口规模。那么,慢启动很快便可能超过这项简化限制。一旦出现拥塞事件,窗口便被大幅降低。如果选择超时模型,还会重新回到一个MSS。这展示了:没有合理的增长与反馈机制,发送端很容易在网络中制造过量数据。

实验三:同样是丢包,为什么恢复曲线不同?

切换到:“比较快速恢复”。你会看到,当拥塞发生时,窗口大约减半。而不是每次都重新回到一个MSS。因此,它的恢复过程通常更加平缓。但这项简化实验并没有完整模拟快速重传的三个重复ACK条件。它只是用于比较:严重超时恢复。与一种保留部分ACK时钟的窗口减半恢复。在真实网络中,这两种情况必须依据具体的ACK和定时器事件来区分。

第十幕:两个人共用一条路,为什么AIMD能够帮助它们逐渐靠近公平?

到现在为止,我们主要观察一个TCP发送端。但网络真正困难的地方,是:资源需要由很多发送端共享。假设两台计算机:A。B。同时通过一条瓶颈链路发送数据。如果只有A,A也许可以独占大部分带宽。但当B开始通信时,两者需要共享这项资源。此时,问题不再只是:怎样让总吞吐量最大?还包括:怎样让共享结果尽可能公平?

一个不公平的初始状态

假设网络当前可以容纳总计:2020个单位的在途数据。但两台发送端的窗口分别为:WA=18W_A=18WB=2W_B=2总和:WA+WB=20W_A+W_B=20刚好没有超出总限制。可是,两者占用十分不均衡。A占90%。B只占10%。如果它们拥有相近的RTT、MSS和其他条件,我们自然希望它们逐渐朝:WA≈WBW_A\approx W_B的方向发展。

为什么两台计算机同时加一,会逐渐改善差距比例?

假设它们都没有收到拥塞信号。都执行加性增加:WA←WA+1W_A\leftarrow W_A+1WB←WB+1W_B\leftarrow W_B+1原来:(18,2)(18,2)变成:(19,3)(19,3)二者绝对差距仍然是:1616但相对比例从:9:19:1变成:19:319:3也就是约:6.33:16.33:1相对不公平程度有所降低。

拥塞时共同乘以0.5,又会怎样?

当两者探测到共享瓶颈发生拥塞,它们可以同时执行乘性减小:(WA,WB)←(0.5WA,0.5WB)(W_A,W_B)\leftarrow(0.5W_A,0.5W_B)例如:(19,3)→(9.5,1.5)(19,3)\rightarrow(9.5,1.5)两者的相对比例保持不变。但绝对差距从16减少到8。之后再进行加性增加:(10.5,2.5)(10.5,2.5)(11.5,3.5)(11.5,3.5)两者会在不断增加与减小的过程中,逐渐减少不公平程度。这就是AIMD公平性的重要直觉之一。

但公平性不是对所有网络都自动成立

1989年,Chiu与Jain发表的理论分析,在明确的资源共享与反馈假设下,展示了加性增加、乘性减小策略的良好收敛性质。但真实TCP连接可能拥有不同的:RTT。MSS。网络瓶颈。路径丢包率。拥塞算法。以及反馈时序。因此,不能简单地说:任何两条TCP连接运行AIMD以后,都会在现实世界里精确分到50%的带宽。公平性依赖模型和条件。不过,在适当的对称条件下,AIMD确实具有非常有价值的公平收敛性质。[ScienceDirect]

用Jain公平指数衡量结果

Raj Jain等研究者发展了一项经典的公平性指标。对于 nn 个用户获得的资源量:x1,x2,…,xnx_1,x_2,\ldots,x_n定义:J=(∑i=1nxi)2n∑i=1nxi2\boxed{ J= \frac{\left(\sum_{i=1}^{n}x_i\right)^2} {n\sum_{i=1}^{n}x_i^2} }当所有用户获得完全相同的资源份额时:J=1J=1若资源分配明显不均衡:J<1J<1对于两个用户:J=(x1+x2)22(x12+x22)\boxed{ J=\frac{(x_1+x_2)^2}{2(x_1^2+x_2^2)} }例如:(x1,x2)=(18,2)(x_1,x_2)=(18,2)那么:J=2022(182+22)J=\frac{20^2}{2(18^2+2^2)}得到:J≈0.610\boxed{J\approx0.610}而如果:(x1,x2)=(10,10)(x_1,x_2)=(10,10)则:J=1\boxed{J=1}

实验:观察两条AIMD连接怎样分享瓶颈

图50-10:两条TCP连接的AIMD共享实验在理想化模型中,两条连接具有相同RTT,并同时接收拥塞信号。没有拥塞时各自增加1,超出总容量阈值时各自乘以0.5。
18 MSS
2 MSS
20 MSS
RTT轮次
20 / 40
A窗口

5.6

B窗口

4.6

Jain公平指数

0.990

当前发送窗口份额54.9% / 45.1%
AB
此实验只演示AIMD的公平收敛直觉。这里把窗口大小当作资源份额的代理,假设两条连接共享相同瓶颈与反馈,且RTT相同;没有模拟真实ACK包、丢包队列、不同延迟或TCP Reno完整协议。

实验结果怎样理解?

将A初始窗口设为18。B设为2。然后观察第0轮与第40轮的公平指数。在该模型中,两条连接的份额会逐渐靠近。但是,窗口并不会在每一轮都平稳增长。当总窗口超过瓶颈阈值,就会发生同步减小。于是,网络资源使用情况出现周期性变化。这说明:拥塞控制追求的不是让每台计算机永远保持一个固定窗口,而是通过反馈,让系统不断适应共享资源的变化。

第十一幕:为什么1988年的论文,不只是一条AIMD公式?

现在,我们重新打开1988年的原始论文。雅各布森在开头写道,经过1986年的严重网络拥塞问题之后,研究者们为4BSD TCP加入了七项新的算法或机制改进。其中包括:
  • 往返时间变化估计。
  • 重传定时器的指数退避。
  • 慢启动。
  • 接收端ACK行为方面的改进。
  • 根据拥塞动态调整发送窗口。
  • Karn提出的重传计时器相关处理规则。
  • 快速重传。
这些并不是完全独立、互不影响的功能。它们共同服务于一个目标:让网络中的数据包流动更加稳定。[cs162.org]

雅各布森将问题归纳为三种情况

论文提出:如果我们希望一个稳定运行的连接遵循数据包守恒原则,那么系统可能在三个地方失败。第一种:连接根本没有正确进入稳定状态。第二种:发送端在旧数据尚未释放相应网络资源时,又过早注入新数据。第三种:由于路径上的资源限制,系统根本无法维持希望达到的稳定工作状态。这三种情况,与我们已经学习的算法,形成了清晰的对应关系。

第一类问题:连接怎样开始?

慢启动解决的是:在尚未建立稳定的ACK反馈节奏时,怎样逐步让数据进入网络?如果一开始就使用过大的发送窗口,瓶颈队列可能遭遇严重突发流量。因此,应当从相对较小的窗口开始,借助确认反馈逐步增长。

第二类问题:为什么发送端过早重传?

RTT变化估计和合理的重传超时,能够减少将排队延迟误认为数据丢失的情况。指数退避则让连续失败的重传逐渐放慢。Karn规则还帮助处理重传之后RTT测量产生的歧义。因此,发送端不必在网络已经拥堵时,反复加入不必要的副本。

第三类问题:网络资源真的不够了怎么办?

拥塞窗口调节负责:根据拥塞反馈,减少发送端允许保留在网络中的未确认数据量。慢启动与拥塞避免负责不同阶段。AIMD则为共享资源的持续探测与退让提供了一种经典方法。

1988年的论文还有真实测试数据

这篇论文不只是抽象的数学推导。作者还包含了数据包轨迹图,用于展示修改前后的TCP行为。例如,论文的一个测试场景涉及通过230.4 Kbit/s点对点链路连接的两台Sun 3/50计算机。在没有慢启动的旧式实现中,数据包出现大量突发发送和重复传输。作者记录,在该测试条件下,只有约35%的可用带宽被有效利用,其余相当大一部分被重复传输等行为浪费。随后,论文展示了改进机制对数据包发送轨迹的影响。[cs162.eecs.berkeley.edu]请注意,这是一项具体实验条件下的测量结果。不能把它理解成:“1980年代所有TCP连接都只能使用35%的带宽。”也不能由此直接推断:“使用慢启动一定让任何网络速度提升到原来的三倍。”具体性能始终取决于网络拓扑、负载、队列、发送窗口和实现方式。

一篇看似讲TCP的论文,其实是在讲反馈控制

从更高的角度看,1988年的论文提出了一种重要的系统设计思路。计算机网络不需要在发送前就精确知道:未来会有多少用户?每个用户会发送多少数据?所有链路的剩余容量是多少?网络何时发生变化?发送端可以从较保守的初始状态开始。观察反馈。调整发送量。再继续观察。因此,它是一种分布式反馈控制系统。
图50-11:TCP拥塞控制是一条反馈回路
发送端调整cwnd网络传输与排队接收端确认数据ACK、延迟与拥塞反馈观测网络 → 更新策略 → 再次发送
查看 SVG 配图原件
示意图原件 50-018-3c21dc800d4b.svg
50-018-3c21dc800d4b.svg · 点击查看大图
这项思想,与我们此前研究的很多算法具有不同的性质。A*在给定图上寻找路径。维特比算法在给定状态模型里寻找最优历史。而TCP拥塞控制,是在一个不断变化、无法被发送端完整观测的环境里,反复调整行动。它不仅在计算一个答案。还在持续参与一个会受到自身行为影响的系统。

第十二幕:Python实战——写一台会慢启动、退让和重新探测的TCP发送端

现在,我们用Python重新实现本章的核心机制。这份代码不需要网络管理员权限。也不需要真正发送TCP数据包。它只模拟拥塞窗口的状态变化。这样,我们可以清楚观察:什么时候发生指数增长?什么时候进入拥塞避免?什么时候出现拥塞?拥塞发生后窗口怎样减小?以及慢启动阈值怎样被修改?

一份完整的教学实现

python

from dataclasses import dataclass


@dataclass
class TcpState:
    cwnd: float = 1.0
    ssthresh: float = 12.0


def simulate_tcp(
    capacity=20,
    rounds=30,
    initial_ssthresh=12,
    loss_mode="timeout"
):
    """
    按RTT聚合的教学版TCP拥塞窗口模拟。

    参数:
        capacity:
            简化路径可以容纳的窗口阈值,单位MSS

        rounds:
            模拟的RTT轮数

        initial_ssthresh:
            初始慢启动阈值

        loss_mode:
            "timeout" -> 超时后cwnd=1
            "fast"    -> 简化快速恢复,cwnd约减半

    不模拟真实ACK、TCP序号、RTO计时、队列及SACK。
    """

    if capacity <= 0:
        raise ValueError("容量阈值必须为正")

    if loss_mode not in ("timeout", "fast"):
        raise ValueError("不支持的loss_mode")

    state = TcpState(
        cwnd=1.0,
        ssthresh=float(initial_ssthresh)
    )

    history = []

    for rtt in range(rounds):

        old_window = state.cwnd
        old_threshold = state.ssthresh

        if old_window > capacity:
            # 教学约定:窗口超过固定阈值,
            # 则触发一次拥塞事件。
            state.ssthresh = max(
                2.0,
                old_window / 2.0
            )

            if loss_mode == "timeout":
                # 超时恢复:重置窗口
                state.cwnd = 1.0
                action = "TIMEOUT"

            else:
                # 简化的窗口减半模型
                state.cwnd = max(
                    1.0,
                    old_window / 2.0
                )
                action = "FAST_LOSS"

        elif old_window < state.ssthresh:
            # 慢启动:一轮近似翻倍
            state.cwnd = min(
                old_window * 2.0,
                state.ssthresh
            )
            action = "SLOW_START"

        else:
            # 拥塞避免:每轮近似加1 MSS
            state.cwnd = old_window + 1.0
            action = "CONGESTION_AVOIDANCE"

        history.append({
            "rtt": rtt,
            "cwnd": old_window,
            "ssthresh": old_threshold,
            "action": action,
            "next_cwnd": state.cwnd,
        })

    return history


history = simulate_tcp(
    capacity=20,
    rounds=30,
    initial_ssthresh=12,
    loss_mode="timeout"
)

print(
    f"{'RTT':>3} "
    f"{'cwnd':>7} "
    f"{'ssthresh':>9} "
    f"{'action':>20} "
    f"{'next':>7}"
)

for row in history:
    print(
        f"{row['rtt']:>3} "
        f"{row['cwnd']:>7.1f} "
        f"{row['ssthresh']:>9.1f} "
        f"{row['action']:>20} "
        f"{row['next_cwnd']:>7.1f}"
    )


# 验证最开始的慢启动
assert history[0]["cwnd"] == 1
assert history[0]["next_cwnd"] == 2

assert history[1]["cwnd"] == 2
assert history[1]["next_cwnd"] == 4

assert history[2]["cwnd"] == 4
assert history[2]["next_cwnd"] == 8

# 验证窗口最终会进入拥塞避免
assert any(
    row["action"] == "CONGESTION_AVOIDANCE"
    for row in history
)

# 验证拥塞后会发生超时恢复
assert any(
    row["action"] == "TIMEOUT"
    for row in history
)

print("全部基础测试通过")

程序的核心在哪里?

真正重要的逻辑,其实只有三个分支。如果发现拥塞:减小阈值。减少拥塞窗口。如果仍处于慢启动:近似翻倍。否则:进入拥塞避免,每轮增加约一个MSS。这三个分支共同构成了我们在互动实验中看到的锯齿曲线。但必须再次强调:真实TCP并不是每经过一个固定时间轮次,就简单地检查:
python
if cwnd > capacity:
真实TCP发送端通常无法直接读取当前整条路径的容量阈值。它需要通过:ACK。丢失。超时。ECN。时延与带宽估计。以及其他反馈信号来调整行为。因此,这份程序演示的是控制逻辑,不是对真实TCP协议栈的完整仿真。

给它再加入两条共享连接

我们还可以实现一个非常简洁的AIMD公平性实验。
python

def jain_fairness(values):
    """
    Jain公平指数。
    两条连接获得相同资源份额时为1。
    """
    total = sum(values)
    squared = sum(x*x for x in values)

    if squared == 0:
        return 1.0

    return total*total / (len(values)*squared)


def simulate_two_flows(
    initial_a=18.0,
    initial_b=2.0,
    capacity=20.0,
    rounds=40
):
    """
    两条具有相同RTT的AIMD连接。

    不拥塞:A和B各增加1
    拥塞:A和B各乘以0.5

    用于观察公平性变化,不是完整TCP模拟。
    """

    a = float(initial_a)
    b = float(initial_b)

    history = []

    for rtt in range(rounds + 1):

        history.append({
            "rtt": rtt,
            "a": a,
            "b": b,
            "fairness": jain_fairness([a, b]),
        })

        if a+b > capacity:
            a *= 0.5
            b *= 0.5
        else:
            a += 1
            b += 1

    return history


history = simulate_two_flows()

print(
    "初始公平指数:",
    round(history[0]["fairness"], 4)
)

print(
    "最终公平指数:",
    round(history[-1]["fairness"], 4)
)

print()

print("RTT  Window-A  Window-B  Fairness")

for row in history[::5]:
    print(
        f"{row['rtt']:>3} "
        f"{row['a']:>8.2f} "
        f"{row['b']:>8.2f} "
        f"{row['fairness']:>8.4f}"
    )

assert history[-1]["fairness"] > history[0]["fairness"]

print("AIMD公平性实验通过")
这段程序能够帮助我们理解:为什么加性增加与乘性减小,在相应条件下能够改善资源分配公平性。但它的实验结果不能被当作真实网络对所有流量都能公平共享带宽的证明。因为它假设了:相同的RTT。相同的拥塞反馈。相同的加减参数。以及同一条瓶颈链路。实际互联网中的流量远比这个模型复杂。

为什么值得亲手修改参数?

因为网络拥塞控制是一类非常适合通过模拟学习的算法。例如:增大初始窗口。会让连接更快探测网络。但也可能更早造成拥塞。减小拥塞窗口。可能改善网络稳定性。但也可能暂时降低传输效率。增大加性增长步长。会加快恢复。但也可能使系统产生更大的振荡。减小乘性退让幅度。可以保留较高发送速率。但也可能让拥塞持续更久。这些变化之间,没有一个在所有网络条件下都绝对最佳的参数组合。因此,真正的网络拥塞控制研究,需要同时考虑:效率。公平性。时延。丢包。以及响应速度。这也是为什么,自1988年之后,TCP拥塞控制算法还在持续演进。

第十三幕:为什么现代互联网没有停留在1988年的锯齿曲线?

如果我们的故事在1988年结束,就会错过TCP拥塞控制后来三十多年的发展。因为互联网发生了巨大的变化。1980年代的网络,常常面对速度相差悬殊的链路。缓冲空间有限。端到端路径复杂。而到了今天,网络可能拥有:千兆乃至更高速率的接入链路。大量移动设备。无线网络。全球范围的数据中心。以及传输高分辨率视频、实时语音和大型数据集的应用。网络能力增长了。但拥塞控制问题并没有消失。因为:只要有限的共享资源仍然需要由多个发送端共同使用,如何协调发送行为就仍然是一项重要问题。

1994年:TCP Vegas开始更加关注时延

1994年,Lawrence Brakmo、Sean O'Malley和Larry Peterson发表了TCP Vegas研究。与主要依靠丢包进行反馈的经典TCP机制相比,Vegas尝试利用实际吞吐量与预期吞吐量之间的差异,较早地判断网络是否开始产生排队。这项研究发表于SIGCOMM 1994,并展示了在论文测试条件下,相比当时Reno实现的性能改善。[数字对象标识符]它代表了一条不同的研究思路:不必总等到数据包丢失以后,才意识到网络可能已经拥堵。

2001年:显式拥塞通知

在传统丢包驱动的拥塞控制中,发送端常常把丢包当作拥塞信号。但如果路由器能够提前察觉队列正在变得拥挤,是否可以直接告诉发送端:请降低发送压力。而不是必须先丢弃某个数据包?这就是:ECN(Explicit Congestion Notification)。即:显式拥塞通知。2001年的RFC 3168,规定了在IP与TCP中使用相关标记的机制。当网络设备检测到适当的拥塞条件,并且数据包支持ECN时,它可以设置相关标记,而不是仅仅依靠丢包表达拥塞。[RFC Editor]这项机制并不意味着:开启ECN之后,网络就永远不会丢包。而是给拥塞控制增加了另一种反馈方式。

2016年:BBR尝试从带宽与往返时间建立模型

2016年,Google研究人员发表:BBR: Congestion-Based Congestion Control。其中有一位作者:Van Jacobson。也就是我们本章的主角。这项研究尝试通过估计:瓶颈带宽。以及路径的传播时延。来构造新的发送控制模型。它不再把“发生丢包”当作判断可用网络容量的唯一核心依据。[Google Research]这里形成了一条非常有趣的历史联系。1988年,雅各布森帮助TCP在拥塞崩溃中建立更加稳定的反馈控制机制。2016年,他又参与了新的拥塞控制研究,试图解决大缓冲队列、高速链路和复杂网络环境中出现的其他问题。不过,BBR本身也存在不同版本和公平性、延迟与复杂网络条件下的研究挑战。它并不是一项在所有环境中都无条件优于其他拥塞算法的方法。

2023年:CUBIC成为标准化的重要拥塞控制算法

另一个非常重要的方向是:CUBIC TCP。2023年8月,IETF发布:RFC 9438 — CUBIC for Fast and Long-Distance Networks。与经典Reno在拥塞避免中采用近似线性窗口增长不同,CUBIC利用三次函数调整窗口增长过程。这样可以更好地适应某些高速、长距离网络。RFC 9438还指出,CUBIC已经被Linux、Windows和Apple的TCP协议栈采用为默认拥塞控制算法。[RFC Editor]因此,不能简单地告诉今天的读者:“现代TCP无一例外地使用1988年的AIMD规则,每个RTT精确加一、丢包后精确减半。”经典AIMD是理解拥塞控制的重要基础。但现代网络的实际实现已经更加丰富。

2021年:QUIC也不能摆脱拥塞控制

现代Web通信中,QUIC同样需要处理:确认。丢失检测。往返时间测量。以及拥塞控制。QUIC不是TCP。它在UDP之上建立了自己的可靠传输、加密与多路复用机制。但它依然需要避免向共享网络注入过量的数据。2021年5月的RFC 9002,专门规定了:QUIC Loss Detection and Congestion Control。它提供了与TCP NewReno类似的经典拥塞控制方案,并允许采用其他符合要求的控制器。[RFC Editor]这说明:拥塞控制并不是TCP独占的问题。它是任何需要在共享网络中控制发送量的传输机制,都必须认真面对的系统问题。

不同时代的控制思想

图50-12:拥塞控制研究的持续演进
时间技术发展核心关注点
1980年代慢启动、经典拥塞避免避免严重拥塞崩溃
1994年TCP Vegas利用RTT等信息感知拥塞
2001年ECN通过显式标记反馈拥塞
2016年BBR带宽与传播时延估计
2021年QUIC拥塞控制规范新传输协议下的反馈控制
2023年CUBIC RFC 9438高速、长距离网络的窗口控制
这是具有代表性的研究与标准化时间线,不是所有拥塞控制算法的完整发明顺序。相关思想存在交叉发展,不同实现也可能长期并存。

那么,1988年的成果今天还重要吗?

非常重要。但它的重要性,未必体现在今天每台计算机都使用完全相同的窗口更新代码。它更重要的遗产,是明确了几项长期有效的原则。发送端需要控制网络中的在途数据。网络反馈应该影响发送行为。启动时不宜完全无视路径容量。丢包与重传不能形成失控的正反馈。计时器必须适应不断变化的RTT。而多台计算机共享网络资源时,各自的局部行为会影响全局稳定性。即使具体拥塞控制算法不断改变,这些问题仍然存在。

第十四幕:1986年的事件,究竟教会了互联网什么?

2012年,范·雅各布森入选:Internet Hall of Fame(互联网名人堂)。官方介绍特别强调了他在TCP拥塞控制、网络性能和互联网扩展方面的长期贡献。除了拥塞控制,他还参与开发过:traceroute。tcpdump。pathchar。等与网络诊断和测量有关的工具。[Internet Hall of Fame]这些工具的共同特点是:帮助工程师理解网络里究竟发生了什么。但它们并不能替代正确的拥塞控制算法。网络测量与拥塞控制,是紧密关联却不同的技术任务。

一张高速网络,为什么仍然可能变慢?

想象今天一条非常高速的网络连接。物理带宽可能达到:10 Gbit/s10\text{ Gbit/s}但如果很多发送端同时注入过量数据。或者排队延迟急剧增长。或者发生大量重传。那么,应用程序看到的有效吞吐量,仍然可能远远低于物理链路速率。因此:链路速率≠应用有效吞吐量\boxed{ \text{链路速率}\ne\text{应用有效吞吐量} }这正是1986年那次网络拥塞崩溃,给后来的研究者留下的重要警示。

“让每个人都尽量快”,为什么不一定让整体最快?

我们可以用一个很简单的例子理解。假设一条狭窄道路,只能以每分钟30辆车的速度稳定通过。如果道路上只有20辆车,交通可能十分顺畅。如果突然有200辆车同时涌向入口,车辆就会形成长队。如果大家还不断抢占入口、阻塞路口,整个道路系统可能反而无法保持正常通行。从每个司机的角度看:自己希望尽快通过。但如果所有人的行动都忽略道路的整体容量,就可能共同制造更低的总效率。对于网络而言,局部发送行为也会通过共享瓶颈影响其他发送端。因此,拥塞控制不仅需要考虑单条连接的速度。还需要考虑:资源利用率。排队延迟。公平性。以及稳定性。

为什么这属于算法问题,而不只是网络设备问题?

因为我们无法要求每台发送端都事先知道整张网络的实时状态。互联网中有大量相互独立的设备和管理主体。网络路径不断变化。用户流量具有不确定性。因此,我们需要一套能够在局部反馈条件下运行的分布式算法。每个发送端根据自己观察到的信息,调整自己的行为。这些局部行为如果设计合理,就有机会共同形成更加稳定的全局结果。当然,这种协调仍然可能受到不合作发送端、不同拥塞算法和复杂网络条件的影响。所以,TCP拥塞控制并不能从数学上保证任何互联网都永远不会拥塞崩溃。但它提供了一项极具价值的机制:让发送端在网络已经承受压力时,不只是继续索取资源,而是学会调整自己的需求。

一项来自工程故障的系统思想

回顾这一章,你可能会发现,雅各布森与Karels的研究,与我们此前学习的纠错编码有一项很有意思的区别。在第47章,汉明希望:当少量数据位发生错误时,让机器自动修复。在第48章,里德与所罗门希望:当一部分符号被破坏时,通过代数关系恢复原始消息。在第49章,维特比希望:从受噪声影响的观测中,推断最可能的合法状态路径。而在第50章:我们甚至不能只依靠把错误全部修复,就解决网络的问题。因为发送端越积极地重传,有时反而可能让共享网络进一步恶化。因此,真正重要的目标,从局部可靠性进一步扩展到了:全局稳定性。

尾声:真正挽救互联网的,不是让它跑得更快,而是教它什么时候应该放慢

1986年10月。加利福尼亚州伯克利。一条本来能够每秒传输约32000比特数据的连接,实际有效吞吐量骤降到每秒40比特。网络仍然存在。计算机仍然运行。数据包仍然被发送。可是,用户真正需要的数据,却很难成功传过来。问题不是没有设备工作。而是太多工作没有产生有用的结果。那些不断重复发送的数据包,本来是为了弥补传输失败。但在严重拥塞时,它们也可能继续占用有限的链路和缓冲资源。那些原本希望让TCP可靠工作的机制,在缺乏合理的拥塞反馈与时序控制时,可能反过来加重网络负担。面对这种现象,雅各布森和Karels没有简单地要求网络中的所有计算机发送得更加努力。他们开始研究:怎样让TCP根据网络当前的能力,调整它允许注入的数据量?怎样让ACK成为帮助发送端控制节奏的反馈?怎样避免过早重传?怎样从很小的窗口开始,迅速而有控制地建立数据流?怎样在拥塞出现时,主动减少发送压力?于是,慢启动、拥塞避免、改进的重传定时器等机制逐渐形成。1988年的论文,将这些研究总结成了一套重要的TCP算法思想。

三个最值得记住的公式

第一个公式,是两类窗口的共同限制:Weffective=min⁡(rwnd,cwnd)\boxed{ W_{\mathrm{effective}}=\min(rwnd,cwnd) }接收端的能力,与网络当前允许的在途数据量,是不同的问题。第二个公式,是带宽时延积:BDP=B×RTT\boxed{BDP=B\times RTT}它帮助我们理解:为什么一条高速、长距离网络,可能需要相当多的在途数据,才能充分利用链路。第三个公式,是经典AIMD:Wt+1={Wt+α,无拥塞反馈βWt,有拥塞反馈\boxed{ W_{t+1}= \begin{cases} W_t+\alpha,&\text{无拥塞反馈}\\ \beta W_t,&\text{有拥塞反馈} \end{cases} }它体现了一种极其重要的反馈控制思想。没有明显拥塞,就逐渐探测可用资源。出现拥塞,就主动降低压力。但这三项公式,并不能单独替代完整的TCP协议。因为真正的网络还涉及:确认机制。排队时延。丢包检测。计时器。链路带宽。窗口更新。以及多条连接之间的相互影响。可靠的网络系统,不是由一条漂亮公式自动产生的,而是由正确的控制原则与大量工程细节共同构成的。

从香农到雅各布森,第四篇完成了怎样的思想旅程?

《穿越噪声的信息》· 第46—50章正式回顾
第46章
香农 · 1948年信息论与信道容量:噪声究竟在什么条件下允许可靠通信?

第47章
汉明 · 1950年重叠校验与伴随式:怎样找出并修复数据中的单比特错误?

第48章
里德与所罗门 · 1960年有限域与多项式编码:怎样恢复受损的多个数据符号?

第49章
维特比 · 1967年动态规划与幸存路径:怎样从有噪声的序列中恢复最有可能的状态历史?

第50章
雅各布森与Karels · 1986—1988年反馈控制与拥塞避免:当许多发送端共享网络时,怎样避免局部行为破坏全局效率?
这五章其实围绕同一个主题。信息怎样穿越一个并不完美的世界?有时,世界的不完美来自噪声。有时,来自数据存储中的损坏。有时,来自我们无法直接看到产生数据的真实状态。还有时,来自许多设备同时争夺有限的传输资源。这些问题表面上不同。但优秀算法往往都需要面对相似的要求。弄清楚系统中哪些信息是可信的。哪些状态必须被保留。哪些资源有限。哪些反馈具有实际意义。以及:在什么条件下,我们能够证明系统具有怎样的能力?

本章留下的最后一个问题

1986年的拥塞崩溃,还揭示了一项比网络协议更加普遍的思想。局部最优行为,不一定构成全局最优结果。对于某个发送端:尽快把自己的文件发送完,当然是合理目标。但如果每一台计算机都只考虑自己的速度,不考虑共享瓶颈的反馈,那么网络整体可能进入严重拥塞。最后,所有人的传输都受到损害。因此,一个分布式系统能否稳定运行,不仅取决于单个参与者拥有多少能力。还取决于它们如何相互协调。这与前面我们研究过的稳定匹配、网络流和资源分配问题,形成了深刻联系。但是,TCP拥塞控制又比静态分配更加复杂。因为资源与需求都在不断变化。今天的最优窗口,明天可能已经不合适。上一轮的网络反馈,下一轮可能不再完全准确。所以,系统必须持续学习环境、修正行为。并允许自己在必要时退让。这也是一项优秀算法的重要品质:不是永远坚持原来的判断,而是根据有意义的反馈,及时改变下一步行动。

一条从实验室通向世界的道路

1986年,一条相距只有数百码的网络通信路径,遭遇了严重的性能崩溃。1988年,相关算法被整理进经典论文。后来,随着互联网规模迅速扩大,拥塞控制成为保证网络稳定运行的重要技术基础。2012年,雅各布森入选互联网名人堂。而今天,虽然现代拥塞控制算法已经发展出CUBIC、BBR及其他方案,但它们仍然需要面对相似的问题。发送得太少,会浪费网络资源。发送得太多,会形成排队与拥塞。过早重传,会浪费容量。不及时重传,又会拖慢可靠数据的恢复。只追求单条连接的速度,可能伤害其他共享连接。只追求完全平均的分配,也未必适合所有不同需求的网络环境。因此,我们需要不断在这些目标之间寻找平衡。这就是TCP拥塞控制留给算法研究者的一项长期课题。有时,让系统运行得更好,并不是要求所有部分都尽可能快。而是让每一个部分,都知道应该在什么时候加速。又应该在什么时候放慢。一张真正可靠的网络,不是永远没有拥塞的网络,而是能够感知反馈、适应变化,并尽可能避免拥塞演变成崩溃的网络。

历史档案:1984—2023年的拥塞控制发展

时间事件重要意义
1984年1月6日John Nagle发表RFC 896已明确讨论IP/TCP拥塞崩溃问题
1986年Raj Jain等人开展相关拥塞避免研究CUTE及其他反馈控制机制的发展
1986年10月LBL—UC Berkeley网络吞吐量严重下降雅各布森和Karels开展深入调查的重要背景
1987年2月新启动机制在IETF介绍当时曾使用Soft Start一名
1987年3月Nagle提出Slow Start名称随后被雅各布森等人采纳
1988年8月Jacobson发表《Congestion Avoidance and Control》系统总结4BSD TCP的多项拥塞控制改进
1989年Chiu与Jain发表增减策略分析从效率、公平性和收敛性研究AIMD
1994年TCP Vegas论文发表探索利用时延及吞吐估计提前判断拥塞
2001年RFC 3168标准化TCP/IP中的显式拥塞通知机制
2009年RFC 5681系统规定经典TCP拥塞控制算法
2011年RFC 6298规范TCP重传超时计算方法
2016年BBR研究发表尝试基于瓶颈带宽与传播时延估计进行控制
2021年RFC 9002定义QUIC的丢失检测与拥塞控制机制
2023年8月RFC 9438CUBIC拥塞控制进入IETF标准轨道

原始论文与主要参考文献

1988年 · 雅各布森原始论文Van Jacobson — Congestion Avoidance and Control(原论文PDF)ACM SIGCOMM 1988,第314—329页。本章最重要的一手资料,包含1986年吞吐量从32 Kbit/s降至40 bit/s的记录,以及慢启动、ACK时钟、重传定时器和AIMD的原始解释。

1984年 · 网络拥塞崩溃的先期讨论John Nagle — RFC 896: Congestion Control in IP/TCP Internetworks用于核查雅各布森研究之前,互联网拥塞崩溃问题已被明确识别和讨论,避免错误归属最早提出者。

1989年 · AIMD效率与公平性理论Dah-Ming Chiu & Raj Jain — Analysis of the Increase/Decrease Algorithms for Congestion Avoidance in Computer Networks《Computer Networks and ISDN Systems》第17卷第1期,第1—14页。分析加减策略在网络资源分配中对效率、公平性和收敛性的影响。

2009年 · 经典TCP拥塞控制标准RFC 5681 — TCP Congestion Control系统区分慢启动、拥塞避免、快速重传和快速恢复,并明确规定cwnd、rwnd、ssthresh及相应控制行为。

2011年 · 重传定时器规范RFC 6298 — Computing TCP's Retransmission Timer描述平滑RTT、RTT变化估计、RTO计算、重传歧义与超时指数退避等机制。

2001年 · 显式拥塞通知RFC 3168 — The Addition of Explicit Congestion Notification (ECN) to IP说明路由器怎样通过标记向端点反馈拥塞,而不仅依赖数据包丢弃。

2016年 · BBR拥塞控制Cardwell、Cheng、Gunn、Hassas Yeganeh、Jacobson — BBR: Congestion-Based Congestion Control发表于《ACM Queue》。展示研究者如何通过瓶颈带宽与传播时延估计,重新思考丢包驱动的拥塞控制策略。

2023年 · CUBIC标准RFC 9438 — CUBIC for Fast and Long-Distance Networks描述广泛部署的CUBIC拥塞控制,以及其与经典线性拥塞窗口增长策略的差异。

2021年 · QUIC拥塞控制RFC 9002 — QUIC Loss Detection and Congestion Control用于理解为什么新的传输协议依然需要可靠的拥塞反馈、丢失检测与发送窗口限制。

人物史料 · 范·雅各布森Internet Hall of Fame — Van JacobsonLawrence Berkeley Laboratory — Computing Sciences History用于核查雅各布森的互联网研究贡献、2012年名人堂入选,以及1986年伯克利实验室拥塞事件的机构历史背景。
**史实与教学重构说明:**1986年10月的32 Kbit/s至40 bit/s数据,来自雅各布森1988年原始论文。论文明确记载LBL与UC Berkeley相距约400码、经过三个IMP跳点。开篇关于研究者观察网络、追查问题的场景,是以原始记录为基础的叙事组织,并非有逐字记录的现场对话。1988年论文正式署名为Van Jacobson,相关算法研究与Mike Karels密切合作。拥塞崩溃概念和AIMD思想均具有更早或同期的研究来源;本章已作区分。两个互动实验与Python程序是现代教学模型。它们采用人为规定的容量阈值和理想化RTT轮次,展示经典窗口控制规则,不是完整的4.3BSD、Tahoe、Reno或现代CUBIC协议实现。实验中的窗口曲线与公平性结果,不应当被理解为对真实互联网吞吐量的定量预测。
第二季 · 第四篇正式完结

《穿越噪声的信息》 · 第46—50章

从香农的信息熵与信道容量,到汉明码、里德—所罗门码、维特比算法,再到雅各布森的TCP拥塞控制,我们完成了从可靠恢复数据走向稳定协调网络资源的五章历程。
下一篇 · 让海量信息找到秩序

第51章:《谁才是互联网最重要的网页?——PageRank的诞生》

1996年,美国加利福尼亚州,斯坦福大学。互联网正在迅速增长。越来越多的人开始建立自己的网站。大学有网站。研究机构有网站。企业有网站。新闻、论坛、个人主页和各种资料,开始进入同一片巨大的信息空间。搜索引擎能够通过关键词找到许多网页。但新的难题出现了。如果一项搜索返回十万个网页,究竟应该先展示哪一个?一个网页包含关键词很多次,是否就意味着它更加重要?如果某个网页被大量其他网页引用,是否能够说明它具有更高的参考价值?又如果引用它的网页本身也很重要,是否应该赋予这项引用更高的权重?斯坦福大学的两位年轻研究者开始研究这些问题。他们叫作:拉里·佩奇(Larry Page)。谢尔盖·布林(Sergey Brin)。他们逐渐发展出一种利用网页之间链接关系进行排序的方法。这项方法后来被称为:PageRank。1998年,佩奇、布林、Rajeev Motwani与Terry Winograd发表了一份技术报告:The PageRank Citation Ranking: Bringing Order to the Web。《PageRank引用排序:为网络带来秩序》。同年,布林和佩奇还发表了关于大型超文本搜索引擎的经典论文,介绍了早期Google系统。 [Google Research][BibBase]下一章,我们将从1990年代的互联网搜索难题开始,研究一个极其反直觉的问题:为什么网页的重要性,不仅取决于有多少人指向它,还取决于指向它的那些网页本身有多重要?我们会构造一张可以点击修改的网页链接网络。然后使用随机游走、马尔可夫链与迭代计算,亲眼观察PageRank怎样从一张复杂的链接图中,逐渐产生稳定的网页重要性排序。第四篇,我们让信息可靠穿越网络。第五篇,我们将开始研究,当海量信息已经抵达世界之后,算法又该怎样帮助人类从中找到真正需要的内容。
——《算法的故事》第二季 · 第50章 完