一、拥塞控制原理简介
由于IP层不向端系统提供显式的网络拥塞反馈,所以TCP的拥塞控制必须使用端到端的拥塞控制而不是网络辅助的拥塞控制。其所采用的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率。如果一个TCP发送方感知到从它到目的地之间的路径上没有什么拥塞,则TCP发送方增加其发送速率;如果发送方感知沿着该路径有拥塞,则发送方就会降低其发送速率。这种方式需要解决三个问题:
- 一个TCP发送方如何限制它向其连接发送流量的速率?
- 一个TCP发送方如何感知从它到目的地之间的路径上存在拥塞?
- 当发送方感知到端到端的拥塞时,采用何种算法来改变其发送速率?
首先分析一下TCP发送方是如何限制其向连接发送流量的:
TCP连接的每一端都是由一个接收缓存、一个发送缓存和几个变量(LastByteRead、rwnd)组成。运行在发送方的TCP拥塞控制机制跟踪一个额外的变量:拥塞窗口(congestion window)。拥塞窗口表示为cwnd,它对一个TCP发送方能向网络中发送流量的速率进行了限制。特别是,在一个发送方中未被确认的数据量不会超过cwnd与rwnd中的最小值,即:
L
a
s
t
B
y
t
e
S
e
n
t
−
L
a
s
t
B
y
t
e
A
c
k
e
d
<
=
m
i
n
∣
c
w
n
d
,
r
w
n
d
∣
LastByteSent-LastByteAcked<=min|cwnd,rwnd|
LastByteSent−LastByteAcked<=min∣cwnd,rwnd∣
我们假设TCP的接收缓存足够大(忽略流量控制的影响),因此在发送方中未被确认的数据仅受限于cwnd。同时假设发送方总是有数据要发送,即在拥塞窗口中的所有报文段要被发送。此时上面的约束限制了发送方中未被确认的数据量,因此间接地限制了发送方的发送速率。
为了理解这一点,我们考虑一个丢包和发送时延均可以忽略不计的连接。此时在每个往返时间(RTT)的起始点,上面的限制条件允许发送方向该连接发送cwnd个字节的数据。在该RTT结束时发送方接收对数据的确认报文。因此,该发送方的发送速率大概是cwnd/RTT字节/秒。通过调节cwnd的值,发送方因此能调整它向连接发送数据的速率。
接下来考虑TCP发送方是如何感知在它与目的地之间的路径上出现了拥塞:
我们将一个TCP发送方的“丢包事件”定义为:要么出现超时,要么收到来自接收方的3个冗余ACK。当出现过度的拥塞时,在沿着这条路径上的一台(或多台)路由器的缓存会溢出,引起一个数据报(包含一个TCP报文段)被丢弃。丢弃的数据报接着会引起发送方的丢包事件(要么超时或收到3个冗余ACK),发送方就认为在发送方到接收方的路径上出现了拥塞的指示。
考虑了拥塞检测问题后,我们接下来考虑网络中没有拥塞这种更为乐观的情况,即没有出现丢包事件的情况:
在此情况下,在TCP的发送方将收到对于以前从未确认报文段的确认。TCP将这些确认的到达作为一切正常的指示,即在网络上传输的报文段正被成功地交付给目的地,并使用这个确认来增加窗口的长度(及其传输速率)。注意如果确认以相当慢的速率到达(例如,如果该端到端路径具有高时延或包含一段低宽链路),则该拥塞窗口将以相当慢的速率增加。在另一方面,如果确认以高速率到达,则该拥塞窗口将会更为迅速的增大。
有了通过调节cwnd值以控制发送速率的机制,关键的问题仍然存在:
TCP发送方怎样确定它“最佳”的发送速率呢?如果众多TCP发送方总体上发送太快,它们能够拥塞网络,导致拥塞崩溃。然而,如果TCP发送方过于谨慎、发送太慢、它们不能充分利用网络的带宽;这就是说,TCP发送方能够以更高的速率发送而不会使网路拥塞。那么TCP发送方如何确认它们的发送速率,既使得网络不会拥塞,与此同时又能充分利用所有可用的带宽?TCP通过下列机制解决这些问题:
- 一个丢失的报文段意味着拥塞,因此当丢失报文段时应当降低TCP发送方的速率。 一个超时事件或四个确认(一个初始ACK和三个冗余ACK)被解释为报文段的丢失。从拥塞控制的观点看,此时TCP发送方应考虑如何减少它的拥塞窗口长度,即减少发送速率,以应对这种推测的丢包事件。
- 一个确认报文段指示该网络正在向接收方交付发送方的报文段,因此,当对先前未确认报文段的确认到达时,能够增加发送方的速率。 确认的到达被认为是一切顺理的隐含指示,即报文段正从发送方成功地交付给接收方,因此该网络不拥塞。拥塞窗口长度因此能够增加。
- 带宽检测。 知道了给定ACK指示源到达目的地路径无拥塞,而丢包事件指示路径拥塞后,TCP调节其传输速率的策略就是增加其速率以响应到达的ACK,而当出现丢包事件时减少传输速率。因此,为了检测出拥塞开始出现的速率,TCP发送方会持续增加它的传输速率,出现了丢包指示(探测到了拥塞,此时的速率就是拥塞开始速率)后速率后退,进而再次开始探测,查看拥塞开始速率是否发生了变化。注意到网络中没有明确的拥塞状态指令,而是将ACK和丢包事件视为隐式信号,并且每个TCP发送方根据异步于其他TCP发送方的本地信息而行动。
二、拥塞控制算法
TCP拥塞控制算法包括三个主要部分:
- 慢启动
- 拥塞避免
- 快速恢复
其中慢启动和拥塞避免是TCP的强制部分,而快速恢复是推荐部分,对TCP的发送方并非是必须的。
2.1 慢启动
当一条TCP连接开始时,cwnd的值通常设置为一个MSS(较小值),这就使得初始发送速率大约为MSS/RTT。由于对于TCP的发送方而言,可用带宽可能比MSS/RTT大得多,TCP发送方希望迅速找到可用带宽的数量。因此,在慢启动状态,cwnd的值以1个MSS开始并且每当运输的报文段首次被确认就增加一个MSS。如下图的例子所示:
有几种结束指数增长的方式:
- 如果存在一个由超时指示的丢包事件(拥塞),TCP发送方将cwnd设置为1并重新开始慢启动过程。它还将第二个状态变量的值ssthresh(“慢启动阈值”的速记)设置为cwnd/2,即当检测到拥塞时将ssthresh设置为拥塞窗口值的一半。
- 第二种方式是直接与ssthresh的值相关联。因为当检测到拥塞时ssthresh设置为cwnd值的一半,每当达到或超过ssthresh的值时,继续使cwnd翻番可能有些鲁莽。因此,当cwnd的值等于ssthresh时,结束慢启动并且TCP转移到拥塞避免模式。
- 最后一种结束慢启动的方式是,如果检测到3个冗余ACK,这时TCP执行一种快速重传并进入快速恢复状态。
2.2 拥塞避免
一旦进入拥塞避免状态,cwnd的值大约是上次遇到拥塞时的值的一半。因此,TCP不应该再每过一个RTT就将cwnd的值翻番,此时TCP采用了一种较为保守的方法,每个rtt只将cwnd的值增加一个MSS。
在拥塞避免状态下,每个丢包事件(由超时或者三个冗余ACK来指示)都指示了应该结束拥塞避免的现性增长:
- 当出现超时时,cwnd的值被设置为1个MSS,同时ssthresh的值被更新为cwnd值的一半
- 当出现三个冗余ACK时,因为网络能继续从发送方向接收方交付报文段,因此TCP对这种丢包事件的行为,相比于超时应该更“宽容”:此时TCP将cwnd的值减半,将ssthresh的值记录为cwnd值的一半,接下来进入快速恢复状态。
下图指示了出现超时事件时cwnd的变化:
2.3 快速恢复
发送方一旦收到3个重复确认,就知道现在只是丢失了个别的报文段,于是不启动慢开始算法,而执行快速恢复算法。
快速恢复是TCP推荐的而非必须的构件。一种称为TCP Tahoe的TCP早期版本,不管是发生超时指示的丢包事件,还是发生3个冗余的ACK指示的丢包事件,都无条件地将其拥塞窗口减至1个MSS,并进入慢启动阶段。TCP的较新版本TCP Reno,则引入了快速恢复的机制。
快速恢复有两种实现方式,第一种是发送方将ssthresh值和拥塞窗口cwnd调整为当前窗口的一半,并开始执行拥塞避免算法;第二种实现方式是把快速恢复开始时的拥塞窗口cwnd的值再增大一些,即等于新的ssthresh+3,这是因为既然发送方收到三个重复的确认,就表明有三个数据报文已经离开了网络;这三个报文段不再消耗网络资源而是停留在接收方的接收缓存中;可见现在网络中不是堆积了报文段而是减少了三个报文段,因此可以适当把拥塞窗口扩大一些。
下图演示了Reno版TCP与Tahoe版TCP的拥塞控制窗口的演化情况。在该图中,阈值初始等于8个MSS,在前8个传输回合,Tahoe和Reno采取了相同的动作。拥塞窗口在慢启动阶段以指数速度快速爬升,在第4轮传输时到达了阈值。然后拥塞窗口以线性速度爬升,直到在第8轮传输后出现了3个冗余ACK。注意到该丢包事件发生时,拥塞窗口值为12MSS。于是ssthresh的值被设置为0.5cwnd=6*MSS。在TCP Reno下,拥塞窗口被设置为cwnd=9MSS,然后线性地增长。在TCP Tahoe下,拥塞窗口被设置为1个MSS,然后呈指数增长,直至到达ssthresh值为止,在这个点它开始线性增长。