TCP拥塞控制
引言
拥塞控制是TCP数据传输中至关重要的一环 如果没有拥塞控制 网络就有可能因为高负载而瘫痪 这显然不是我们希望看到的行为。
首先拥塞控制中我们需要知道什么是拥塞 TCP/IP详解卷一中给拥塞下的定义是这样的 路由器无法处理高速率到达的流量而被迫丢弃数据信息的现象 从这个定义中我们至少可以知道对于路由器来说当收包的速率持续高与发包时 哪怕路由器本身可存储一部分数据 但是当到了一定限度时路由器无法再存储多余的数据 这个时候就会丢弃掉多余的包 也就是发生拥塞现象(丢包当然不止这一种情况)
首先在进行拥塞控制的学习之前我们需要知道一个原理 即包守恒原理, 即在这个网络传输的系统中 一个包不会凭空消失和出现 拥塞控制就是建立在包守恒原理之上的 即出现拥塞代表当先系统中包过多 我们需要减缓发送方速率,这就是拥塞控制的原理,听起来容易做起来难 这里涉及到不少问题 首先我们先抛出几个问题 :
1. 如何准确的判断出现拥塞
2. 如何减缓TCP的传输速率
3. 如何在减缓后恢复到最佳速率
首先判断拥塞中最简单的方法即在出现丢包时我们推断出现拥塞现象 如何判断出现丢包呢 就是前一篇博客中所讨论的重传现象,无论是超时重传也好,快速重传也好,都是推测出丢包以后所进行的应对措施,那么当丢包情况出现时TCP还做了哪些工作呢,当然不止是简单的重传,其中最重要的就是拥塞控制
拥塞窗口
当出现重传时我们推测出现了拥塞 从而要有一定的措施去应对 我们前面提到了包守恒原理 拥塞出现即系统中的包多了 这个时候我们要做的就是减少发送端发送速率 那么该如何减少呢 这个时候引入了拥塞窗口这个概念 。
我们知道有两点可以影响发送端的发送速率 一个是接收端的缓冲区大小 这个可以通过数据包中的通告窗口很容易的获得 还有一个就是当前的网络传输速率决定 这个是我们无法直接获取的 最好的获取方式便莫过于通过不停的发包 以此得到的数据在发送端维护一个变量 我们称之为拥塞窗口(congestion window (cwnd)),这样通过维护这样一个窗口我们可以在拥塞窗口和通告窗口的共同作用下维护滑动窗口,决定发送数据的大小。
但是问题来了 我们该如何维护拥塞窗口呢 我们无法直接获得cwnd的直接值 因为网络的状态当然不是静态的 所以我们需要去测量一个合适的值 那么如何测量和什么才是合适的值这又是两个问题 我们首先了来回答第二个问题,即什么值才是适合的值,这里引入一个概念带宽延迟积(BandWidth-Dealy Product BDP) 即理想吞吐量
理想吞吐量(bit) =路径带宽(bit/秒为单位) *往返时间( RTT ) (以秒为单位)
例如: 路径带宽是1Gbps,往返时间( RTT )是100毫秒,则 吞吐量 = (102410241024) * 0.1秒 = 107374183 bit = 13421773 byte/s
而实际的吞吐量则是窗口大小/RTT 在窗口大小为65535的时候百分之九十九的带宽都被浪费了
所以我们的最佳窗口大小就是BDP。
下一个问题接踵而至 即如何测量?
这个问题的解答就是相信大家都有所耳闻的经典方法
慢启动与拥塞避免
这两个算法是TCP的核心算法 是基于包守恒原理的 它们不在同一时间点运行 而是基于一个慢启动阈值(slow start threshold (ssthresh)),当当前cwnd小于阈值时执行慢启动 当cwnd大于阈值时执行拥塞避免 其在传输的开始有一个初始值。
慢启动
在传输的初始阶段 网络的传输能力是未知的 如果大量的数据包短时间进入网络就会造成拥塞 为了避免这种情况在传输的初始阶段我们把cwnd设置为一个较小的值 一般为N*SMSS(发送端最大段)&& N <= 4 让其小于阈值,从而进行慢启动
慢启动的主要过程就是在慢启动中 没有丢包的情况下每个数据包都有响应的ACK 每个ACK都会使得cwnd加一个SWSS 即可以多发一个包 意味着每次收到包以后可以发送的包翻倍 这样就会以指数的形式增加cwnd 当遇到丢包的时候 意味着出现阻塞 这个时候阻塞窗口减半(一般),阈值重新计算,继续上述过程 直到到达拥塞避免 即cwnd大于阈值,值得注意的是 在建立连接之初和超时重传发生是一定会执行慢启动 超时重传时有理由怀疑cwnd不能准确反映当前的网络的拥塞状态 就会设置cwnd为重启窗口(Restart Window RW) ,一般小于阈值
到达阀值意味着有很多的资源可以使用 但是如果继续慢启动以cwnd指数上升的话 就会很快占用全部资源 从而出现丢包 为了防止这种情况 引入了拥塞避免
拥塞避免
一旦进入拥塞避免 那么cwnd的增长速度相比与慢启动就会变的很慢
换而言之 每当收到一个不重复的ACK时就会对cwnd进行这样的线性更新
cwnd(n) = cwnd(n-1) + SMSS * SMSS / cwnd(n-1)
设cwnd(n-1)为 K*SMSS
化简得
cwnd(n) = cwnd(n-1) + (1/k)×SMSS
即在拥塞避免时cwnd每次只是增长K分之1 相比于慢启动的指数来看真的慢了很多 这样可以更好的逼近最优窗口 获取相对最优的吞吐量(当前吞吐量 = RTT × 当前发送窗口大小) (高速环境下还可以工作良好吗?)
但是在遇到快速重传的时候如何工作呢
- ssthresh = max( 在外数据值 / 2 ,2*SMSS )
- 进行快速重传 cwnd的值减为原来的一半值
- 每收到一个重复的ACK 就把cwnd的值加1(Reno的快速恢复)
- 收到一个非重复ACK的时候 即恢复到正常网络状态 cwnd的值设置为ssthresh
Reno算法
这里提到了Reno算法 我们刚开始抛出的第三个问题在这里就可以解决了
遵循包守恒原理 因为cwnd已经减半 即发送速率减小 而收到一个ACK 证明可以再发一个数据包 这就是Reno算法中快速恢复算法的理论依据 即每收到一个ACK cwnd就会临时增长1SMSS 直到收到一个非重复ACK 证明网络已经恢复正常 cwnd的快速增长就会停止 执行上述第四步
NewReno算法
快速重传可能会出现这样一种情况 就是出现多个包的丢失 即进行多次快速重传 但是Reno算法中一旦收到一个非重复的ACK 即一个包重传成功 就会使得cwnd的膨胀停止 并减小至一个特定的值 这样就会使得窗口减小 这样有可能导致网络中较为空闲 有可能无法触发快速重传(三个重复ACK才会触发(一般)) 就会导致超时重传 从而引发慢启动 使得cwnd被减小为重启窗口 从而使得吞吐量减小 所以提出了NewReno算法
做出的改进就是记录一个恢复点,即已发送的最大序列号,当到达恢复点之前不停止快速重传 这样就会减少上述情况的发生
Eifel响应算法
说到重传不得不说伪重传 倘若出现这种情况那么为拥塞控制做的一切不就没用了吗?因为伪重传检测算法无论如何也要等到重传包发送出去以后才能检测到伪重传,而那时拥塞控制已经生效 那么出现伪重传该怎么办呢 答案是在检测到超时重传的时候 会撤销对
ssthresh和cwnd的修改,
就是在因为超时而需要修改ssthresh时 都需要维护一个特殊的值
pipe_prev = min(在外数据值, ssthresh)
倘若出现伪重传 会修改cwnd与ssthresh
1. cwnd = 在外数据值 + min( 初始窗口值,bytes_acked )
2. ssthresh = pipe_prev
第一步的原因是因为无论任何情况下多发送一个初始窗口的新数据也被认为是安全的
BIC-TCP算法
在大的BDP网络中使用我们前面所提到的算法显然在达到最优窗口之前浪费大量的吞吐量,这种算法能够在能够在特殊的环境下达到更快的发送速度,提高吞吐量
BIC-TCP主要在发送端使用了两种算法二分搜索增大(binary search increase)和加法增大(additive increase),在确定出现了拥塞后调用。我们可以想一个问题 当BDP很大的时候,如果使用一般的算法可能会出现发了很多包 窗口仍然小于最优窗口,因为拥塞避免的增长速率很小 它的优点在吞吐量这个角度反而成了劣势,所以我们可以使最小的窗口为最近一次在一个RTT内没有出现丢包的窗口 最大窗口为最近一次丢包的窗口 最优窗口肯定在这两个值之间 二分可以以很好的效率得到最优窗口大小 那么出现了一个问题 有可能当前窗口距最优窗口距离较大 那么盲目的选择中间值可能会造成大量的包短时间进入网络 造成拥塞 加法增大就在这个时候派上用场 当中间点与当前窗口差值大于一个特定值时采取加法增大 当到达这个特定值时使用二分增大 特殊值由一个比例因子与cwnd决定
基于延迟的拥塞控制算法
上面所阐述的方法都是基于丢包的 也就是说所谓的拥塞信号就是丢包 那么拥塞一定是通过丢包才能确定吗 当然不是,可以想象一种情况 就是当网络中发生拥塞的时候 那么可以想象RTT一定会不断增长 因为在路由器中会进入等待队列 这个其实也可以当做一种信号,有几种拥塞控制算法就是基于这种原理
Vegas算法
算法维护两个阈值 形成一个区间 用每次传输的吞吐量与两个阈值对比 小于左区间 则增大窗口 大于右区间 则减小窗口 以此做到维护拥塞窗口
但是这种情况也有弊端 就是可能拥塞发生相反的反向 这样一来RTT任然会增大,但是两个反向一般链路不同 有不同的拥塞状态
FAST算法
与Vegas差不多 都是基于吞吐量来调整窗口,与前者不同的是它还依赖当前性能与实际的预期的不同来调整窗口 FAST算法在每一个RTT更新发送率
ECN算法
ECN又称为显式拥塞通知(Explicit Congestion Notification (ECN))
就是在路由器上管接收理队列称为积极队列管理 可对经过的数据报在IP数据报上设置ECN位 接收端通过向发送端返回一个ACK来通知拥塞状况 因为ACK数据包是不可靠的 当接收端接收到一个带有ECN的数据报时 会设置 ECN回显 ECN-echo字段,直到接收到一个发送端发送来的带有CWR标记一个字节的的数据包 代表拥塞窗口已经减小 这种方法可以使得更早的检测到拥塞 从而在丢包前降低发送速率