TCP拥塞控制:慢启动,拥塞避免,快重传/快恢复

前言:

  用快重传/快恢复而不是快重传和快恢复这种说法,是从RFC5681文档继承的,个人理解这两种说法是有区别的,快重传/快恢复是把这两种机制混合使用作为一种拥塞控制的方法,而后者是把两者都单独作为拥塞控制的一种方法。
  因为慢启动和拥塞避免还是相对易于理解的,本文并没有对慢启动和拥塞避免做详细的介绍,只是说明了在启动它们的时候TCP具体的动作,对快重传/快恢复是翻译了RFC5681文档的相关部分,并在最后的总结部分说明了自己的理解。

需要明晰的几个概念:

  FlightSize:已发送但并未被ack确认的数据量
  SMSS: sender MSS
  ssthresh(慢启动阈值的速记) = max (FlightSize / 2, 2*SMSS) ----公式1
  SND.UNA:就是还没有收到ack的地方,也就是重复ACK指定的包
  cwnd:拥塞窗口 cwnd congestion window,一个RTT内允许发送的最大数据量
  RTT:Round-Trip Time
  如何检测网络拥塞:把计时器超时和重复ACK导致的报文丢失/乱序作为网络拥塞的一种表现。

慢启动:

  采取的动作:将拥塞窗口(cwnd congestion window)设置为1个MSS,每经过一个rtt时,cwnd=cwnd * 2
  结束慢启动的三个条件:
  1. 检测到超时指示的丢包事件
    这时采取的动作是ssthresh=cwnd/2,cwnd=1
  2. cwnd>=ssthresh时
    进入拥塞避免模式
  3. 接收到3个重复ack指示的丢包事件:
    ssthresh=cwnd/2, cwnd=ssthresh+3*MSS,重传丢失的包并进入快恢复

拥塞避免:

  采取的动作:
    每经过一个rtt时,使cwnd=cwnd+1个MSS,一种通用的方法是:
    每次接收一个ack,则cwnd=cwnd+MSS*(MSS/cwnd)个字节,比如MSS是1460,而cwnd是14600,那么会在一个rtt内发送10个报文段,每次接收一个ack,则cwnd增加1/10个MSS,这样10个ACK之后,拥塞窗口的值会增加一个ack
  拥塞避免结束的条件:
  1. 超时指示的丢包事件:
    这时采取的动作是ssthresh=cwnd/2,cwnd=1,并进入慢启动
  2. 3个重复ack指示的丢包事件:
    cwnd=cwnd/2,ssthresh=cwnd,重传丢失的包并进入快恢复

快重传/快恢复:翻译自RFC5681

快重传/快恢复采取的行为:
  1. 在发送端接收到前两个重复ack后,tcp会在满足
    1> receiver’s advertised window允许
    2> FlightSize<=cwnd+2*SMSS(sender mss)
    3> 新数据可用于传输
的条件下传输新数据(RFC 3042)。进一步地,TCP sender必不能改变cwnd以反映这两个发送的分节。(这里的意思是对于前两个重复ACK都会这么做,会重复两次上述操作,而不只是在接收到两个重复ACK之后做一次)
  2. 当收到第三个重复ACK时,必须设定ssthresh为不超过公式1的值,当启用了RFC3042时,以受限传输算法发送的额外分节必不能用于此公式的计算
  3. 重传SND.UNA指示的包,并把cwnd设为ssthresh+3,个人理解加3的原因是因为收到3个重复的ACK,表明有3个已被ACK的数据包离开了网络,不占用网络资源(详见RFC5681)。
  4. 对每个接收到的额外的重复ACK(在第三个之后),cwnd=cwnd+1,加1的原因同上,表明既然收到了一个ACK,那么就有一个失序报文被确认存放在了接收缓冲区,离开了网络不占用网络资源
  5. 当有未发送的数据可用并且cwnd的新值和receiver’s advertised window允许的情况下,TCP应该发送1个MSS大小的新报文
  6. 当下一个确认了之前未被确认的ACK到达时,TCP必须将cwnd设置为ssthresh(在步骤2中设置的)。
  此ACK应该是从第3步的重传引起的确认,即重传后的一个RTT(尽管在接收方出现数据分节的严重无序交付的情况下它可能更快到达)。此外,此ACK应该确认丢失的段和第三重复ACK的接收之间发送的所有中间段(如果没有丢失)。
注意:该算法已被发现通常无法从单个数据包飞行(这里我也不知道怎么翻译了,直接硬译,见谅)中的多个丢失中有效恢复[FF96]。(原文:This algorithm is known to generally not recover efficiently from multiple losses in a single flight of packets) 。下文(RFC5681)第4.3节解决了这种情况。

最后给出拥塞控制的完整FSM(Finite State Machine 有限状态机)描述:摘自计算机网络–自顶向下方法
  横线上方表示引起状态变迁的条件,无语句代表无条件
  横线下方表示状态变迁所采取的行为,无语句代表无行为
  箭头表示从一种状态变迁到另一种状态
  虚线箭头表示初始状态
TCP拥塞控制:慢启动,拥塞避免,快重传/快恢复

总结

  慢启动和拥塞避免比较好理解,看自顶向下或者网上的一些文章就差不多可以理解了。快恢复在书上没有详细的展开来讲,只好去网上翻文章参考,翻来翻去和书上的描述还是对不上,干脆去翻RFC文档,结果真香。
  说下自己对快重传/快恢复的理解把,官网对它们的描述标题是快重传/快恢复,而不是快重传和快恢复,注意这两个说法的区别,这说明这两个机制是混合当作一个拥塞控制方法用的。文档读下来,个人理解快重传/快恢复的思想是把重复ACK当作即可能是网络拥塞导致的丢包导致的结果,也可能是报文失序导致的结果,而且既然在RTO(Re-Transmission-Timer)超时前可以收到3个重复的ACK,说明网络还是可以的,并没有超时,那就不能把这种情况当作严重的网络拥塞来处理(比如直接把cwnd设为1),并假设此时网络是不拥塞的,还要继续发送新的报文,如果此时网络确实拥塞,那么RTO超时进入慢启动状态,如果不拥塞,那么一直会收到重复ACK并在RTO超时前收到乱序/丢失报文的确认,此时就在不影响接下来报文发送的情况下快速恢复了丢失/乱序报文。
  当然,文章最后也说明了这种算法有时并不能奏效,因为这种方法是针对只有一个包乱序/丢失的情况,如果有多个报文丢失/乱序,那么这时就会出问题了,详见这里
  另外,感觉网上的一些文章对于快重传快恢复并没有说出它的精髓,也就是在不影响接下来报文传输的情况下完成对失序/丢失报文的重传,难道是我对RFC的理解出问题了么,如果有知道的朋友请告知下。

参考书籍/文章:

TCP那些事
计算机网络–自顶向下方法
RFC5681
RFC3042