传输层 IV(TCP协议——流量控制、拥塞控制)【★★★★】
(★★)代表非常重要的知识点,(★)代表重要的知识点。
一、TCP 流量控制(★★)
1. 利用滑动窗口实现流量控制
一般说来,我们总是希望数据传输得更快一些。但如果发送方把数据发送得过快,接收方就可能来不及接收,这就会造成数据的丢失。所谓流量控制(flow control)就是让发送方的发送速率不要太快,要让接收方来得及接收,因此可以说流量控制是一个速度匹配服务(匹配发送方的发送速率与接收方的读取速率)。
利用滑动窗口机制可以很方便地在 TCP 连接上实现对发送方的流量控制,滑动窗口的基本原理已在数据链路层中介绍过【数据链路层 II(流量控制与可靠传输机制)【★★★★★】】,这里要介绍的是 TCP 如何使用窗口机制来实现流量控制。
TCP 要求发送方维持一个接收窗口 rwnd(receiver window,即接收方允许连续接收的能力,单位是字节),接收方根据当前接收缓存的大小,动态地调整接收窗口的大小,其大小反映了接收方的容量。接收方将其放在 TCP 报文段首部中的“窗口”字段,以通知发送方。发送方的发送窗口不能超过接收方给出的接收窗口值,以限制发送方向网络注入报文的速率。
下面通过下图所示的例子说明如何利用滑动窗口机制进行流量控制。
假设数据只从 A 发往 B ,而 B 仅向 A 发送确认报文段。在连接建立时,B 可通过设置确认报文段首部中的窗口字段来将 rwnd 通知给 A 。发送方 A 总是根据最新收到的 rwnd 值来限制自己发送窗口的大小,从而将未确认的数据量控制在 rwnd 大小之内,保证 A 不会使 B 的接收缓存溢出(发送方的发送窗口不能超过接收方给出的接收窗口的数值)。B 告诉了 A:“ 我的接收窗口 rwnd = 400”。请注意, TCP 的窗口单位是字节,不是报文段。
TCP 连接建立时的窗口协商过程在图中没有显示出来。再设每一个报文段为 100 字节长,而数据报文段序号的初始值设为 1(见图中第一个箭头上面的序号 seq == 1 ,图中右边的注释可帮助理解整个过程)。请注意,图中箭头上面大写 ACK 表示首部中的确认位 ACK ,小写 ack 表示确认字段的值。
我们应注意到,接收方的主机 B 进行了三次流量控制。第一次把窗口减小到 rwnd = 300 ,第二次又减到 rwnd = 100 ,最后减到 rwnd = 0 ,即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机 B 重新发出一个新的窗口值为止。我们还应注意到, B 向 A 发送的三个报文段都设置了 ACK = 1, 只有在 ACK = 1 时确认号字段才有意义。
现在我们考虑一种情况。在上图中, B 向 A 发送了零窗口的报文段后不久, B 的接收缓存又有了一些存储空间。于是 B 向 A 发送了 rwnd = 400 的报文段。然而这个报文段在传送过程中丢失了。A 一直等待收到 B 发送的非零窗口的通知,而 B 也一直等待 A 发送的数据。如果没有其他措施,这种互相等待的死锁局面将一直延续下去。
为了解决这个问题, TCP 为每一个连接设有一个持续计时器(persistence timer)。只要 TCP 连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带 1 字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。如果窗口仍然是零,那么收到这个报文段的一方就重新设置持续计时器。如果窗口不是零,那么死锁的僵局就可以打破了。
注: TCP 规定,即使设置为零窗口,也必须接收以下几种报文段:零窗口探测报文段、确认报文段和携带紧急数据的报文段。
传输层和数据链路层的流量控制的区别是:
- 传输层实现的是端到端,即两个进程之间的流量控制;
- 数据链路层实现的是两个中间的相邻结点之间的流量控制。
- 此外,数据链路层的滑动窗口协议的窗口大小不能动态变化;传输层的窗口大小则可以动态变化。
2. TCP 的传输效率(拓展)
前面已经讲过,应用进程把数据传送到 TCP 的发送缓存后,剩下的发送任务就由 TCP 来控制了。可以用不同的机制来控制 TCP 报文段的发送时机。例如:
- 第一种机制是 TCP 维持一个变量,它等于最大报文段长度 MSS(Maximum Segment Size)。只要缓存中存放的数据达到 MSS 字节时,就组装成一个 TCP 报文段发送出去。
- 第二种机制是由发送方的应用进程指明要求发送报文段,即 TCP 支持的推送(push)操作。
- 第三种机制是发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过 MSS)发送出去。
但是,如何控制 TCP 发送报文段的时机仍然是一个较为复杂的问题。例如,一个交互式用户使用一条 TELNET 连接(运输层为 TCP 协议)。假设用户只发 1 个字符,加上 20 字节的首部后,得到 21 字节长的 TCP 报文段。再加上 20 字节的 IP 首部,形成 41 字节长的 IP 数据报。在接收方TCP 立即发出确认,构成的数据报是 40 字节长(假定没有数据发送)。若用户要求远地主机回送这一字符,则又要发回 41 字节长的 IP 数据报和 40 字节长的确认 IP 数据报。这样,用户仅发 1 个字符时,线路上就需传送总长度为 162 字节共 4 个报文段。当线路带宽并不富裕时,这种传送方法的效率的确不高。因此应适当推迟发回确认报文,并尽量使用捎带确认的方法。
在 TCP 的实现中广泛使用 Nagle 算法。算法如下:若发送应用进程把要发送的数据逐个字节地送到 TCP 的发送缓存,则发送方就把第一个数据字节先发送出去,把后面到达的数据字节都缓存起来。当发送方收到对第一个数据字符的确认后,再把发送缓存中的所有数据组装成一个报文段发送出去,同时继续对随后到达的数据进行缓存。只有在收到对前一个报文段的确认后才继续发送下一个报文段。当数据到达较快而网络速率较慢时,用这样的方法可明显地减少所用的网络带宽。Nagle 算法还规定,当到达的数据已达到发送窗口大小的一半或已达到报文段的最大长度时,就立即发送一个报文段。这样做,就可以有效地提高网络的吞吐量。
另一个问题叫做糊涂窗口综合征(silly window syndrome),有时也会使 TCP 的性能变坏。设想一种情况: TCP 接收方的缓存已满,而交互式的应用进程一次只从接收缓存中读取 1 个字节(这样就使接收缓存空间仅腾出 1 个字节),然后向发送方发送确认,并把窗口设置为 1 个字节(但发送的数据报是 40 字节长)。接着,发送方又发来 1 个字节的数据(请注意,发送方发送的 IP 数据报是 41 字节长)。接收方发回确认,仍然将窗口设置为 1 个字节。这样进行下去,使网络的效率很低。
要解决这个问题,可以让接收方等待一段时间,使得或者接收缓存已有足够空间容纳一个最长的报文段,或者等到接收缓存已有一半空闲的空间。只要出现这两种情况之一,接收方就发出确认报文,并向发送方通知当前的窗口大小。此外,发送方也不要发送太小的报文段,而是把数据积累成足够大的报文段,或达到接收方缓存的空间的一半大小。
上述两种方法可配合使用。使得在发送方不发送很小的报文段的同时,接收方也不要在缓存刚刚有了一点小的空间就急忙把这个很小的窗口大小信息通知给发送方。
二、TCP 拥塞控制(★★)
1. 拥塞控制的一般原理
拥塞控制是指防止过多的数据注入网络,保证网络中的路由器或链路不致过载。出现拥塞时端点并不了解拥塞发生的细节,对通信的端点来说,拥塞往往表现为通信时延的增加。
在计算机网络中的链路容量(即带宽)、交换结点中的缓存和处理机等,都是网络的资源。在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫做拥塞(congestion)。可以把出现网络拥塞的条件写成如下的关系式:
∑ 对资源的需求 > 可用资源
若网络中有许多资源同时呈现供应不足,网络的性能就要明显变坏,整个网络的吞吐量将随输入负荷的增大而下降。
有人可能会说:“只要任意增加一些资源,例如,把结点缓存的存储空间扩大,或把链路更换为更高速率的链路,或把结点处理机的运算速度提高,就可以解决网络拥塞的问题。”其实不然。这是因为网络拥塞是一个非常复杂的问题。简单地采用上述做法,在许多情况下,不但不能解决拥塞问题,而且还可能使网络的性能更坏。
网络拥塞往往是由许多因素引起的。例如,当某个结点缓存的容量太小时,到达该结点的分组因无存储空间暂存而不得不被丢弃。现在设想将该结点缓存的容量扩展到非常大,于是凡到达该结点的分组均可在结点的缓存队列中排队,不受任何限制。由于输出链路的容量和处理机的速度并未提高,因此在这队列中的绝大多数分组的排队等待时间将会大大增加,结果上层软件只好把它们进行重传(因为早就超时了)。由此可见,简单地扩大缓存的存储空间同样会造成网络资源的严重浪费,因而解决不了网络拥塞的问题。
又如,处理机处理的速率太慢可能引起网络的拥塞。简单地将处理机的速率提高,可能会使上述情况缓解一些,但往往又会将瓶颈转移到其他地方。问题的实质往往是整个系统的各个部分不匹配。只有所有的部分都平衡了,问题才会得到解决。
拥塞常常趋于恶化。如果一个路由器没有足够的缓存空间,它就会丢弃一些新到的分组。但当分组被丢弃时,发送这一分组的源点就会重传这一分组,甚至可能还要重传多次。这样会引起更多的分组流入网络和被网络中的路由器丢弃。可见拥塞引起的重传并不会缓解网络的拥塞,反而会加剧网络的拥塞。
2. 拥塞控制与流量控制的区别
拥塞控制与流量控制的关系密切,它们之间也存在着一些差别。
-
所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。但TCP 连接的端点只要迟迟不能收到对方的确认信息,就猜想在当前网络中的某处很可能发生了拥塞,但这时却无法知道拥塞到底发生在网络的何处,也无法知道发生拥塞的具体原因。(是访问某个服务器的通信量过大?还是在某个地区出现自然灾害?)
-
相反,流量控制往往是指点对点通信量的控制,是个端到端的问题(接收端控制发送端)。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
当然,拥塞控制和流量控制也有相似的地方,即它们都通过控制发送方发送数据的速率来达到控制效果(告诉发送端网络已出现麻烦,必须放慢发送速率)。
可以用一个简单例子说明这种区别。设某个光纤网络的链路传输速率为 1000 Gbit/s 。有一台巨型计算机向一台个人电脑以 1 Gbit/s 的速率传送文件。显然,网络本身的带宽是足够大的,因而不存在产生拥塞的问题,但如此高的发送速率将导致 PC 可能来不及接收,因此必须进行流量控制(巨型计算机必须经常停下来,以便使个人电脑来得及接收)。
但如果有另一个网络,其链路传输速率为 1 Mbit/s ,而有 1000 台大型计算机连接在这个网络上。假定其中的 500 台计算机分别向其余的 500 台计算机以 100 kbit/s 的速率发送文件。那么现在的问题已不是接收端的大型计算机是否来得及接收,而是整个网络的输入负载是否超过网络所能承受的范围。
3. 开环控制和闭环控制(拓展)
进行拥塞控制需要付出代价。这首先需要获得网络内部流量分布的信息。在实施拥塞控制时,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。这样就产生了额外开销。拥塞控制有时需要将一些资源(如缓存、带宽等)分配给个别用户(或一些类别的用户)单独使用,这样就使得网络资源不能更好地实现共享。十分明显,在设计拥塞控制策略时,必须全面衡量得失。
在下图中的横坐标是提供的负载(offered load),代表单位时间内输入给网络的分组数目。因此提供的负载也称为输入负载或网络负载。纵坐标是吞吐量(throughput),代表单位时间内从网络输出的分组数目。具有理想拥塞控制的网络,在吞吐量饱和之前,网络吞吐量应等于提供的负载,故吞吐量曲线是 45° 的斜线。但当提供的负载超过某一限度时,由于网络资源受限,吞吐量不再增长而保持为水平线,即吞吐量达到饱和。这就表明提供的负载中有一部分损失掉了(例如,输入到网络的某些分组被某个结点丢弃了)。虽然如此,在这种理想的拥塞控制作用下,网络的吞吐量仍然维持在其所能达到的最大值。
但是,实际网络的情况就很不相同了。从上图可看出,随着提供的负载的增大,网络吞吐量的增长速率逐渐减小。也就是说,在网络吞吐量还未达到饱和时,就已经有一部分的输入分组被丢弃了。当网络的吞吐量明显地小于理想的吞吐量时,网络就进入了轻度拥塞的状态。更值得注意的是,当提供的负载达到某一数值时,网络的吞吐量反而随提供的负载的增大而下降,这时网络就进入了拥塞状态。当提供的负载继续增大到某一数值时,网络的吞吐量就下降到零,网络已无法工作,这就是所谓的死锁(deadlock)。
从原理上讲,寻找拥塞控制的方案无非是寻找使不等式(∑ 对资源的需求 > 可用资源)不再成立的条件。这或者是增大网络的某些可用资源(如业务繁忙时增加一些链路,增大链路的带宽,或使额外的通信量从另外的通路分流),或减少一些用户对某些资源的需求(如拒绝接受新的建立连接的请求,或要求用户减轻其负荷,这属于降低服务质量)。但正如上面所讲过的,在采用某种措施时,还必须考虑到该措施所带来的其他影响。
实践证明,拥塞控制是很难设计的,因为它是一个动态的(而不是静态的)问题。当前网络正朝着高速化的方向发展,这很容易出现缓存不够大而造成分组的丢失。但分组的丢失是网络发生拥塞的征兆而不是原因。在许多情况下,甚至正是拥塞控制机制本身成为引起网络性能恶化甚至发生死锁的原因。这点应特别引起重视。
由于计算机网络是一个很复杂的系统,因此可以从控制理论的角度来看拥塞控制这个问题。这样,从大的方面看,可以分为开环控制和闭环控制两种方法。开环控制就是在设计网络时事先将有关发生拥塞的因素考虑周到,力求网络在工作时不产生拥塞。但一旦整个系统运行起来,就不再中途进行改正了。
闭环控制是基于反馈环路的概念,主要有以下几种措施:
- 监测网络系统以便检测到拥塞在何时、何处发生。
- 把拥塞发生的信息传送到可采取行动的地方。
- 调整网络系统的运行以解决出现的问题。
有很多的方法可用来监测网络的拥塞。主要的一些指标是:由于缺少缓存空间而被丢弃的分组的百分数、平均队列长度、超时重传的分组数、平均分组时延、分组时延的标准差,等等。上述这些指标的上升都标志着拥塞的增长。
一般在监测到拥塞发生时,要将拥塞发生的信息传送到产生分组的源站。当然,通知拥塞发生的分组同样会使网络更加拥塞。
另一种方法是在路由器转发的分组中保留一个比特或字段,用该比特或字段的值表示网络没有拥塞或产生了拥塞。也可以由一些主机或路由器周期性地发出探测分组,以询问拥塞是否发生。
此外,过于频繁地采取行动以缓和网络的拥塞,会使系统产生不稳定的振荡。但过于迟缓地采取行动又不具有任何实用价值。因此,要采用某种折中的方法。但选择正确的时间常数是相当困难的。
4. TCP 的拥塞控制方法
TCP 进行拥塞控制的算法有四种,即慢开始(slow-start)、拥塞避免(congestion avoidance)、快重传(fast retransmit)和快恢复(fast recovery)。下面就介绍这些算法的原理。为了集中精力讨论拥塞控制,我们假定:
- 数据是单方向传送的,对方只传送确认报文。
- 接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度来决定。
下面讨论的拥塞控制也叫做基于窗口的拥塞控制。发送方在确定发送报文段的速率时,既要考虑接收方的接收能力,又要从全局考虑不要使网络发生拥塞。为此,除了之前介绍的接收窗口 rwnd,TCP 还要求发送方维持一个叫做拥塞窗口 cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。
发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就可以再增大一些,以便把更多的分组发送出去,这样就可以提高网络的利用率。但只要网络出现拥塞或有可能出现拥塞,就必须把拥塞窗口减小一些,以减少注入到网络中的分组数,以便缓解网络出现的拥塞。
发送方又是如何知道网络发生了拥塞呢?我们知道,当网络发生拥塞时,路由器就要丢弃分组。因此只要发送方没有按时收到应当到达的确认报文,也就是说,只要出现了超时,就可以猜想网络可能出现了拥塞。现在通信线路的传输质量一般都很好,因传输出差错而丢弃分组的概率是很小的(远小于 1% )。因此,判断网络拥塞的依据就是出现了超时。
接收窗口的大小可根据 TCP 报文首部的窗口字段通知发送方,而发送方如何维护拥塞窗口呢?这就是下面讲解的慢开始和拥塞避免算法。为了便于理解,下面采用最大报文段长度 MSS 作为拥塞窗口大小的单位。
1)慢开始和拥塞避免
① 慢开始算法
慢开始算法的思路是这样的:当主机开始发送数据时,由于并不清楚网络的负荷情况,所以如果立即把大量数据字节注入到网络,那么就有可能引起网络发生拥塞。经验证明,较好的方法是先探测一下,若没有发生拥塞,则适当增大拥塞窗口,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。
【拓展】旧的规定是这样的:在刚刚开始发送报文段时,先把初始拥塞窗口 cwnd 设置为 1 至 2个发送方的最大报文段 SMSS(Sender Maximum Segment Size)的数值,但新的 RFC 5681 把初始拥塞窗口 cwnd 设置为不超过 2 至 4 个 SMSS 的数值。具体的规定如下:
I、若 SMSS > 2190 字节:则设置初始拥塞窗口 cwnd = 2 × SMSS 字节,且不得超过 2 个报文段。
II、若(SMSS > 1095 字节)且(SMSS ≤ 2190 字节):则设置初始拥塞窗口 cwnd = 3 × SMSS 字节,且不得超过 3 个报文段。
III、若 SMSS ≤ 1095 字节:则设置初始拥塞窗口 cwnd = 4 × SMSS 字节,且不得超过 4 个报文段。
可见这个规定就是限制初始拥塞窗口的字节数。
慢开始规定,在每收到一个对新的报文段的确认后,可以把拥塞窗口增加最多一个 SMSS 的数值。更具体些,就是:
拥塞窗口 cwnd 每次的增加量 = min(N , SMSS)
其中 N 是原先未被确认的、但现在被刚收到的确认报文段所确认的字节数。不难看出,当 N < SMSS 时,拥塞窗口每次的增加量要小于 SMSS 。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。
下面用例子说明慢开始算法的原理。请注意,虽然实际上 TCP 是用字节数作为窗口大小的单位。但为叙述方便起见,我们用报文段的个数作为窗口大小的单位,这样可以使用较小的数字来阐明拥塞控制的原理。
例如,发送方向接收方发送数据,在一开始发送方先设置 cwnd = 1 ,发送第一个报文段 M1 ,接收方收到后确认 M1 。发送方收到对 M1 的确认后,把 cwnd 从 1 增大到 2,于是发送方接着发送 M2 和 M3 两个报文段。接收方收到后发回对 M2 和 M3 的确认。发送方每收到一个对新报文段的确认(重传的不算在内)就使发送方的拥塞窗口加 1 ,因此发送方在收到两个确认后, cwnd 就从 2 增大到 4 ,并可发送 M4 ~ M7 共 4 个报文段(见下图所示) 。因此使用慢开始算法后,每经过一个传输轮次(transmission round),即往返时间 RTT ,拥塞窗口 cwnd 就会加倍,即 cwnd 的值随传输轮次指数增长。
这里我们使用了一个名词——传输轮次。从上图可以看出,一个传输轮次所经历的时间其实就是往返时间 RTT(请注意, RTT 并非是恒定的数值)。使用“传输轮次”是更加强调:把拥塞窗口 cwnd 所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。例如,拥塞窗口 cwnd 的大小是 4 个报文段,那么这时的往返时间 RTT 就是发送方连续发送 4 个报文段,并收到这 4 个报文段的确认后总共经历的时间。
我们还要指出,慢开始的“慢”并不是指 cwnd 的增长速率慢,而是指在 TCP 开始发送报文段时先设置 cwnd = 1 ,使得发送方在开始时只发送一个报文段(目的是试探一下网络的拥塞情况),然后再逐渐增大 cwnd 。这当然比设置大的 cwnd 值一下子把许多报文段注入到网络中要“慢得多”。这对防止网络出现拥塞是一个非常好的方法。
顺便指出,上图只是为了说明慢开始的原理。在 TCP 的实际运行中,发送方只要收到一个对新报文段的确认,其拥塞窗口 cwnd 就立即加 1 ,并可以立即发送新的报文段,而不需要等这个轮次中所有的确认都收到后(如上图所示的那样)再发送新的报文段。
② 拥塞避免算法
拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢地增大,即每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加 1 ,而不是像慢开始阶段那样加倍增长。因此在拥塞避免阶段就有“加法增大”(Additive Increase, AI )的特点。这表明在拥塞避免阶段,拥塞窗口 cwnd 按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
注:请注意,因为现在是讲原理,把窗口的单位改为报文段的个数。实际上应当是“拥塞窗口仅增加一个 MSS 的大小,单位是字节”。在具体实现拥塞避免算法的方法时可以这样来完成:只要收到一个新的确认,就使拥塞窗口 cwnd 增加(MSS × MSS / cwnd)个字节。例如,假定 cwnd 等于 10 个 MSS 的长度,而 MSS 是 1460 字节。发送方可一连发送 14600 字节(即 10 个报文段)。假定接收方每收到一个报文段就发回一个确认。于是发送方每收到一个新的确认,就把拥塞窗口稍微增大一些,即增大 0.1 MSS = 146 字节。经过一个往返时间 RTT(或一个传输轮次)后,发送方共收到 10 个新的确认,拥塞窗口就增大了 1460 字节,正好是一个 MSS 的大小。
为了防止拥塞窗口 cwnd 增长过大引起网络拥塞,还需要设置一个慢开始门限 ssthresh 状态变量(阈值)。慢开始门限 ssthresh 的用法如下:
- 当 cwnd < ssthresh 时,使用上述的慢开始算法。
- 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
- 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法。
③ 网络拥塞的处理
无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(未按时收到确认)就要首先把慢开始门限 ssthresh 设置为出现拥塞时的发送方的 cwnd 值的一半(但不能小于 2),然后把拥塞窗口 cwnd 重新设置为 1 ,执行慢开始算法。这样做的目的是迅速减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够时间把队列中积压的分组处理完。
下图用具体例子说明了在拥塞控制的过程中,TCP 的拥塞窗口 cwnd 是怎样变化的。图中的的数字 ① 至 ⑤ 是特别要注意的几个点。现假定 TCP 的发送窗口等于拥塞窗口。为了便于理解,图中的窗口单位不使用字节而使用报文段的个数。
-
当 TCP 连接进行初始化时,把拥塞窗口 cwnd 置为 1 。在本例中,慢开始门限的初始值设置为 16 个报文段,即 ssthresh = 16 。
-
在执行慢开始算法时,发送方每收到一个对新报文段的确认 ACK ,就把拥塞窗口值加 1 ,然后开始下一轮的传输(请注意,下图的横坐标是传输轮次 RTT ,不是时间),此时拥塞窗口 cwnd 随着传输轮次按指数规律增长。
-
当拥塞窗口 cwnd 增长到慢开始门限值 ssthresh 时(图中的点 ① ,此时拥塞窗口 cwnd = 16),就改为执行拥塞避免算法,拥塞窗口按线性规律增长。但请注意,“拥塞避免”并非完全能够避免了拥塞,利用该方法要完全避免网络拥塞是不可能的,“拥塞避免”是说把拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞。
-
当拥塞窗口 cwnd = 24 时,网络出现了超时(图中的点 ②),发送方判断为网络拥塞。于是调整门限值 ssthresh = cwnd / 2 = 12 ,同时设置拥塞窗口 cwnd= 1 ,重新进入慢开始阶段。按照慢开始算法,发送方每收到一个对新报文段的确认 ACK ,就把拥塞窗口值加 1 。当拥塞窗口 cwnd = ssthresh = 12 时(图中的点 ③,这是新的 ssthresh 值),改为执行拥塞避免算法,拥塞窗口按线性规律增大。
【注】:在慢开始阶段,若 2 × cwnd > ssthresh ,则下一个 RTT 后的 cwnd 等于 ssthresh ,而不等于 2cwnd ,第 16 个轮次时 cwnd = 8、ssthresh = 12,则第 17 个轮次时 cwnd = 12 ,而不等于 16 。
- 当拥塞窗口 cwnd = 16 时(图中的点 ④) ,出现了一个新的情况,就是发送方一连收到 3 个对同一个报文段的重复确认(图中记为 3-ACK)。关于这个问题要解释如下:
有时,个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口 cwnd 又设置为 1 ,因而降低了传输效率。采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失。
在慢开始和拥塞避免算法中使用了“乘法减小”和“加法增大”方法。“乘法减小”是指不论是在慢开始阶段还是在拥塞避免阶段,只要出现超时(即很可能出现了网络拥塞),就把慢开始门限值 ssthresh 设置为当前拥塞窗口的一半(并执行慢开始算法)。当网络频繁出现拥塞时,ssthresh 值就下降得很快,以大大减少注入网络的分组数。而“加法增大”是指执行拥塞避免算法后,在收到对所有报文段的确认后(即经过一个 RTT),就把拥塞窗口 cwnd 增加一个 MSS 大小,使拥塞窗口缓慢增大,以防止网络过早出现拥塞。
2)快重传和快恢复
① 快重传算法
采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失,快重传算法是使发送方尽快进行重传,而不等超时计时器超时再重传。这就要求接收方不要等待自己发送数据时才进行捎带确认,而要立即发送确认,即使收到了失序的报文段也要立即发出对已收到报文段的重复确认。发送方一旦连续收到 3 个冗余 ACK(即重复确认),就立即重传相应的报文段,而不等该报文段的超时计时器超时再重传。
如下图所示,接收方收到了 M1 和 M2 后都分别及时发出了确认。现假定接收方没有收到 M3 但却收到了 M4 。本来接收方可以什么都不做。但按照快重传算法,接收方必须立即发送对 M2 的重复确认,以便让发送方及早知道接收方没有收到报文段 M3 。发送方接着发送 M5 和 M6 。接收方收到后也仍要再次分别发出对 M2 的重复确认。这样,发送方共收到了接收方的 4 个对 M2 的确认,其中后 3 个都是重复确认。快重传算法规定,发送方只要一连收到 3 个重复确认,就知道接收方确实没有收到报文段 M3 ,因而应当立即进行重传(即“快重传”),这样就不会出现超时,发送方也不就会误认为出现了网络拥塞。使用快重传可以使整个网络的吞吐量提高约 20% 。
② 快恢复算法
快恢复算法的原理如下:当发送方连续收到 3 个冗余 ACK(重复确认)时,执行“乘法减小”方法,把慢开始门限 ssthresh 调整为当前 cwnd 的一半。这是为了预防网络发生拥塞。但发送方现在认为网络很可能没有发生(严重)拥塞,否则就不会有几个报文段连续到达接收方,也不会连续收到重复确认。因此与慢开始算法的不同之处是,它把 cwnd 值也调整为当前 cwnd 的一半(即等于 ssthresh 值),然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。
注:虽然 RFC 5681 给出了根据已发送出但还未被确认的数据的字节数来设置 ssthresh 的新的计算公式,即 ssthresh = max {FlightSize / 2 , 2 × SMSS} 。这里 FlightSize 是正在网络中传送的数据量。但在讨论拥塞控制原理时,我们就采用这种把问题简化的方法(即把慢开始门限减半)来表述。
因此,在下图中的点 ④ ,发送方知道现在只是丢失了个别的报文段。于是不启动慢开始,而是执行快恢复算法。这时,发送方调整门限值 ssthresh = cwnd / 2 = 8 ,同时设置拥塞窗口 cwnd = ssthresh = 8(见下图中的点 ⑤) ,并开始执行拥塞避免算法。
因为跳过了拥塞窗口 cwnd 从 1 起始的慢开始过程,所以被称为快恢复。快恢复算法的实现过程如下图所示,作为对比,虚线为慢开始的处理过程。
【拓展】:请注意,也有的快恢复实现是把快恢复开始时的拥塞窗口 cwnd 值再增大一些(增大 3 个报文段的长度),即等于新的 ssthresh + 3 × MSS 。这样做的理由是:既然发送方收到 3 个重复的确认,就表明有 3 个分组已经离开了网络。这 3 个分组不再消耗网络的资源而是停留在接收方的缓存中(接收方发送出 3 个重复的确认就证明了这个事实)。可见现在网络中并不是堆积了分组而是减少了 3 个分组。因此可以适当把拥塞窗口扩大些。
从上图可以看出,在拥塞避免阶段,拥塞窗口是按照线性规律增大的,这常称为加法增大(Additive Increase, AI )。而一旦出现超时或 3 个重复的确认,就要把门限值设置为当前拥塞窗口值的一半,并大大减小拥塞窗口的数值。这常称为“乘法减小“(Multiplicative Decrease, MD)。二者合在一起就是所谓的 AIMD 算法。
为什么超时事件发生时 cwnd 被置为1 ,而收到 3 个冗余 ACK 时 cwnd 减半?
大家可以从如下角度考虑。超时事件发生和收到 3 个冗余 ACK ,哪个意味着网络拥塞程度更严重?通过分析不难发现,在收到 3 个冗余 ACK 的情况下,网络虽然拥塞,但至少还有 ACK 报文段能被正确交付。而当超时发生时,说明网络可能已经拥塞得连 ACK 报文段都传输不了,发送方只能等待超时后重传数据。因此,超时事件发生时,网络拥塞更严重,那么发送方就应该最大限度地抑制数据发送量,所以 cwnd 置为 1 ;收到 3 个冗余 ACK 时,网络拥塞不是很严重,发送方稍微抑制一下发送的数据量即可,所以 cwnd 减半。
3)总结
实际上,这四种算法同时应用在 TCP 拥塞控制机制中,它们使用的总结:在 TCP 连接建立和网络出现超时时,采用慢开始和拥塞避免算法(ssthresh = cwnd / 2, cwnd = 1);当发送方收到 3 个冗余 ACK 时,采用快重传和快恢复算法(ssthresh = cwnd / 2, cwnd = ssthresh)。
根据以上所述, TCP 的拥塞控制可以归纳为下图的流程图。
在这一节的开始我们就假定了接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度来决定。但实际上接收方的缓存空间总是有限的。接收方根据自己的接收能力设定了接收方窗口 rwnd ,并把这个窗口值写入 TCP 首部中的窗口字段,传送给发送方。因此,接收方窗口又称为通知窗口(advertised window)。因此,从接收方对发送方的流量控制的角度考虑,发送方的发送窗口一定不能超过对方给出的接收方窗口值 rwnd 。
即在流量控制中,发送方发送数据的量由接收方决定;而在拥塞控制中,则由发送方自己通过检测网络状况来决定。由于接收方的缓存空间总是有限的,因此,如果把拥塞控制和接收方对发送方的流量控制一起考虑,那么很显然,发送方发送窗口的大小由流量控制和拥塞控制共同决定。
当题目中同时出现接收窗口(rwnd)和拥塞窗口(cwnd)时,发送方发送窗口的上限值应当取为接收方窗口 rwnd 和拥塞窗口 cwnd 这两个变量中较小的一个。也就是说:
发送方窗口的上限值= Min [ rwnd, cwnd ]
当 rwnd < cwnd 时:是接收方的接收能力限制发送方窗口的最大值。
反之,当 cwnd < rwnd 时:则是网络的拥塞程度限制发送方窗口的最大值。
也就是说, rwnd 和 cwnd 中数值较小的一个,控制了发送方发送数据的速率。
三、例题
① 在 TCP 协议中,发送方的窗口大小取决于( C )。
A. 仅接收方允许的窗口
B. 接收方允许的窗口和发送方允许的窗口
C. 接收方允许的窗口和拥塞窗口
D. 发送方允许的窗口和拥塞窗口
② 以下关于 TCP 窗口与拥塞控制概念的描述中,错误的是( C )。
A. 接收窗口(rwnd)通过 TCP 首部中的窗口字段通知数据的发送方
B. 发送窗口确定的依据是:发送窗口 = min [接收端窗口,拥塞窗口]
C. 拥塞窗口是接收端根据网络拥塞情况确定的窗口值
D. 拥塞窗口大小在开始时可以按指数规律增长
【拥塞窗口是发送端根据网络拥塞情况确定的窗口值。】
③ 若甲向乙发起了一条 TCP 连接,最大段长为 1KB ,乙每收到一个数据段都会发出一个接收窗口为 10KB 的确认段,若甲在 t 时刻发生超时,此时拥塞窗口为 16KB 。则从 t 时刻起,在不再发生超时的情况下,经过 10 个 RTT 后,甲的发送窗口是( A )。
A. 10KB
B. 12KB
C. 14KB
D. 15KB
【拥塞窗口为 16KB ,则新的 ssthresh = 8KB 。由于接收窗口为 10KB ,发送方窗口的上限值应为拥塞窗口和接收方窗口两个变量中的较小值。】
【因此,经过 10 个 RTT 后,甲的发送窗口 = min [10, 15] = 10 。】
④ 设 TCP 的拥塞窗口的慢开始门限值初始为 8(单位为报文段),当拥塞窗口上升到 12 时发生超时,TCP 开始慢开始和拥塞避免,则第 13 次传输时拥塞窗口的大小为( C )。
A. 4
B. 6
C. 7
D. 8
【拥塞窗口的大小变化应为:1 2 4 8 9 10 11 12 1 2 4 6 7】
⑤ 假设一个 TCP 连接的传输过程在慢开始阶段,在 t RTT 时刻到 (t + 1) RTT 时刻之间发送了 k 个数据段,假设仍然保持在慢开始阶段,预期在 (t + 1) RTT 时刻到 (t + 2) RTT 时刻之间将发送( D )个数据段(假设接收方有足够的缓存)。
A. k
B. k+1
C. 2k
D. 2k
⑥ 下列关于 TCP 的拥塞控制机制,描述错误的是( C )。
A. TCP 刚建立连接进入慢开始阶段
B. 慢开始阶段拥塞窗口指数级增加
C. 超时发生时,新门限值(慢开始和拥塞避免阶段的分界点)等于旧门限值的一半
D. 拥寒避免阶段拥塞窗口线性增加
【超时发生时,新门限值通常设置为此时拥塞窗口值的一半,而不是旧门限值的一半。】
⑦ 在一个 TCP 连接中,MSS 为 1KB ,当拥塞窗口为 34KB 时收到了 3 个冗余 ACK 报文。若在接下来的 4 个 RTT 内报文段传输都是成功的,则当这些报文段均得到确认后,拥塞窗口的大小是(D )。
A. 8KB
B. 16KB
C. 20KB
D. 21KB
【条件“收到了 3 个冗余 ACK 报文”说明此时应执行快恢复算法。拥塞窗口的大小变化应为:17 18 19 20 21 。】
⑧ 甲向乙发起一个 TCP 连接,最大段长 MSS = 1KB ,RTT = 3ms,乙的接收缓存为 16KB 且乙的接收缓存仅有数据存入而无数据取出,则甲从连接建立成功至发送窗口达到 8KB ,需经过的最小时间以及此时乙的接收缓存的可用空间分别为( B )。
A. 3ms, 15KB
B. 9ms, 9KB
C. 6ms, 13KB
D. 12ms, 8KB
⑨ 【2009 统考真题】一个 TCP 连接总以 1KB 的最大段长发送 TCP 段,发送方有足够多的数据要发送,当拥塞窗口为 16KB 时发生了超时,若接下来的 4 个 RTT 时间内的 TCP 段的传输都是成功的,则当第 4 个 RTT 时间内发送的所有 TCP 段都得到肯定应答时,拥塞窗口大小是( C )。
A. 7KB
B. 8KB
C. 9KB
D. 16KB
⑩ 【2010 统考真题】主机甲和主机乙之间已建立一个 TCP 连接,TCP 最大段长为 1000B 。若主机甲的当前拥塞窗口为 4000B ,在主机甲向主机乙连续发送两个最大段后,成功收到主机乙发送的第一个段的确认段,确认段中通告的接收窗口大小为 2000B ,则此时主机甲还可以向主机乙发送的最大字节数是( A )。
A. 1000
B. 2000
C. 3000
D. 4000
【发送方的发送窗口的上限值取接收窗口和拥塞窗口这两个值中的较小一个,于是此时发送方的发送窗口为 min{4000, 2000} = 2000B 。因为确认段是对第一个段的确认,所以 2000B 的含义是甲发送第一个段后还能再发送 2000B,又因为之前甲连续发送了两个最大段,也就是说,第二个段还未收到确认,所以甲还能继续向乙发送的最大字节数是 2000 - 1000 = 1000B 。】
(11) 【2014 统考真题】主机甲和乙建立了 TCP 连接,甲始终以 MSS = 1KB 大小的段发送数据,并一直有数据发送;乙每收到一个数据段都会发出一个接收窗口为 10KB 的确认段。若甲在 t 时刻发生超时的时候拥塞窗口为 8KB ,则从 t 时刻起,不再发生超时的情况下,经过 10 个 RTT 后,甲的发送窗口是( A )。
A. 10KB
B. 12KB
C. 14KB
D. 15KB
(12) 【2015 统考真题】主机甲和主机乙新建一个 TCP 连接,甲的拥塞控制初始阅值为 32KB ,甲向乙始终以 MSS = 1KB 大小的段发送数据,并一直有数据发送;乙为该连接分配 16KB 接收缓存,并对每个数据段进行确认,忽略段传输延迟。若乙收到的数据全部存入缓存,不被取走,则甲从连接建立成功时刻起,未出现发送超时的情况下,经过 4 个 RTT 后,甲的发送窗口是( A )。
A. 1KB
B. 8KB
C. 16KB
D. 32KB
(13) 【2017统考真题】若甲向乙发起一个 TCP 连接,最大段长 MSS = 1KB ,RTT = 5ms ,乙开辟的接收缓存为 64KB ,则甲从连接建立成功至发送窗口达到 32KB ,需经过的时间至少是( A )。
A. 25ms
B. 30ms
C. 160ms
D. 165ms
(14) 【2019 统考真题】某客户通过一个 TCP 连接向服务器发送数据的部分过程如下图所示。客户在 t0 时刻第一次收到确认序列号 ack_seq = 100 的段,并发送序列号 seq = 100 的段,但发生丢失。若 TCP 支持快速重传,则客户重新发送 seq = 100 段的时刻是( C )。
A. t1
B. t2
C. t3
D. t4
(15) 【2020 统考真题】若主机甲与主机乙已建立一条 TCP 连接,最大段长(MSS)为 1KB ,往返时间(RTT)为 2ms ,则在不出现拥塞的前提下,拥塞窗口从 8KB 增长到 32KB 所需的最长时间是( D )。
A. 4ms
B. 8ms
C. 24ms
D. 48ms
(16) 【2021 统考真题】设主机甲通过 TCP 向主机乙发送数据,部分过程如下图所示。甲在 t0 时刻发送一个序号 seq = 501、封装 200B 数据的段,在 t1 时刻收到乙发送的序号 seq = 601 、确认序号 ack_seg = 501 、接收窗口 rcvwnd = 500B 的段,则甲在未收到新的确认段之前可以继续向乙发送的数据序号范围是( C )。
A. 501~1000
B. 601~1100
C. 701~1000
D. 801~1100
【主机甲发送 200B 数据的段后,继续发送数据的段的序号 seg = 701 。因为甲收到乙发来的确认序号为 501 、接收窗口为 500 的段,即从序号 501 开始,甲累积还能发送 500B 的数据。因为甲此时已发送从序号 501 开始的 200B 数据,所以甲在未收到新的确认段之前,还能发送的数据字节数为 500 - 200 = 300B ,还能发送的数据序号范围是 701 ~ 1000 。】
(17) 【2022 统考真题】假设主机甲和主机乙已建立一个 TCP 连接,最大段长 MSS = 1KB ,甲一直向乙发送数据,当甲的拥塞窗口为 16KB 时,计时器发生了超时,则甲的拥塞窗口再次增长到 16KB 所需要的时间至少是( C )。
A. 4 RTT
B. 5 RTT
C. 11 RTT
D. 16 RTT