010: 能不能说说 TCP 的拥塞控制?

上一节所说的流量控制发生在发送端跟接收端之间,并没有考虑到整个网络环境的影响,如果说当前网络特别差,特别容易丢包,那么发送端就应该注意一些了。而这,也正是拥塞控制需要处理的问题。

对于拥塞控制来说,TCP 每条连接都需要维护两个核心状态:

  • 拥塞窗口(Congestion Window,cwnd)
  • 慢启动阈值(Slow Start Threshold,ssthresh)

涉及到的算法有这几个:

  • 慢启动
  • 拥塞避免
  • 快速重传和快速恢复

接下来,我们就来一一拆解这些状态和算法。首先,从拥塞窗口说起。

拥塞窗口

拥塞窗口(Congestion Window,cwnd)是指目前自己还能传输的数据量大小。

那么之前介绍了接收窗口的概念,两者有什么区别呢?

  • 接收窗口(rwnd)是接收端给的限制
  • 拥塞窗口(cwnd)是发送端的限制

限制谁呢?

限制的是发送窗口的大小。

有了这两个窗口,如何来计算发送窗口

发送窗口大小 = min(rwnd, cwnd)

取两者的较小值。而拥塞控制,就是来控制cwnd的变化。

慢启动

刚开始进入传输数据的时候,你是不知道现在的网路到底是稳定还是拥堵的,如果做的太激进,发包太急,那么疯狂丢包,造成雪崩式的网络灾难。

因此,拥塞控制首先就是要采用一种保守的算法来慢慢地适应整个网路,这种算法叫慢启动。运作过程如下:

  • 首先,三次握手,双方宣告自己的接收窗口大小
  • 双方初始化自己的拥塞窗口(cwnd)大小
  • 在开始传输的一段时间,发送端每收到一个 ACK,拥塞窗口大小加 1,也就是说,每经过一个 RTT,cwnd 翻倍。如果说初始窗口为 10,那么第一轮 10 个报文传完且发送端收到 ACK 后,cwnd 变为 20,第二轮变为 40,第三轮变为 80,依次类推。

难道就这么无止境地翻倍下去?当然不可能。它的阈值叫做慢启动阈值,当 cwnd 到达这个阈值之后,好比踩了下刹车,别涨了那么快了,老铁,先 hold 住!

在到达阈值后,如何来控制 cwnd 的大小呢?

这就是拥塞避免做的事情了。

拥塞避免

原来每收到一个 ACK,cwnd 加1,现在到达阈值了,cwnd 只能加这么一点: 1 / cwnd。那你仔细算算,一轮 RTT 下来,收到 cwnd 个 ACK, 那最后拥塞窗口的大小 cwnd 总共才增加 1。

也就是说,以前一个 RTT 下来,cwnd翻倍,现在cwnd只是增加 1 而已。

当然,慢启动拥塞避免是一起作用的,是一体的。

快速重传和快速恢复

快速重传

在 TCP 传输的过程中,如果发生了丢包,即接收端发现数据段不是按序到达的时候,接收端的处理是重复发送之前的 ACK。

比如第 5 个包丢了,即使第 6、7 个包到达的接收端,接收端也一律返回第 4 个包的 ACK。当发送端收到 3 个重复的 ACK 时,意识到丢包了,于是马上进行重传,不用等到一个 RTO 的时间到了才重传。

这就是快速重传,它解决的是是否需要重传的问题。

选择性重传

那你可能会问了,既然要重传,那么只重传第 5 个包还是第5、6、7 个包都重传呢?

当然第 6、7 个都已经到达了,TCP 的设计者也不傻,已经传过去干嘛还要传?干脆记录一下哪些包到了,哪些没到,针对性地重传。

在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就可以加上SACK这个属性,通过left edgeright edge告知发送端已经收到了哪些区间的数据报。因此,即使第 5 个包丢包了,当收到第 6、7 个包之后,接收端依然会告诉发送端,这两个包到了。剩下第 5 个包没到,就重传这个包。这个过程也叫做选择性重传(SACK,Selective Acknowledgment),它解决的是如何重传的问题。

快速恢复

当然,发送端收到三次重复 ACK 之后,发现丢包,觉得现在的网络已经有些拥塞了,自己会进入快速恢复阶段。

在这个阶段,发送端如下改变:

  • 拥塞阈值降低为 cwnd 的一半
  • cwnd 的大小变为拥塞阈值
  • cwnd 线性增加

以上就是 TCP 拥塞控制的经典算法: 慢启动拥塞避免快速重传和快速恢复

微信公众号: 前端三元同学

获取更多资料/联系加交流群