上一篇文章,我们介绍了 Nagle 算法和滑动窗口协议 他们用来让接收方实现流量控制。
本文我们来介绍几个发送方进行流量控制的算法和策略
滑动窗口协议中的通告窗口用来实现接收方的流量控制,而慢启动算法所使用的拥塞窗口则用来实现发送方的流量控制。 当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小) 每收到一个ACK, 拥塞窗口就增加到原来报文段的 2 倍(cwnd 以字节为单位,慢启动以报文段大小为单位进行增加) 发送方取拥塞窗口与通告窗口中的最小值作为发送上限。
上一篇日志中我们提到了带宽时延积,用来作为窗口大小设置的参考,这里我们详细介绍一下: BDP(bit) = link_bandwidth(bps) * RTT(s)
如果我们将发送端与接收端之间的连接想象成一条管道,带宽描述的是管道的粗细,RTT 则描述了这个管道的长度,而 BDP 则描述了这个管道的实际容量。 理想状态下,任何时刻这个管道中的 ACK 的数目总是相等的,发出报文的速度与接收报文的速度刚好像等。 当管道被发送的数据填满,那么就造成了拥塞,典型的情况是发送端带宽大于接收端带宽。
顾名思义,这个算法是为了避免 TCP 连接的拥塞而设计的,他的基本假设是一旦分组丢失,就意味着拥塞的发生,通常与慢启动一起使用,他们需要维持两个变量:拥塞窗口 cwnd 和慢启动门限 ssthresh。 算法的工作过程如下: 1. 初始时 cwnd 为 1 个报文段大小,ssthresh 为 65535 字节,由于限制发送方取拥塞窗口与通告窗口中的最小值作为发送上限,因此首次发送只能发送一个报文段 2. 当发生超时或收到重复确认时,ssthresh 被设置为当前窗口大小(cwnd 与通告窗口大小的最小值且大于等于 2)的一半 3. 如果发生超时则将 cwnd 重新设为 1 个报文段大小 4. 每收到一个 ACK,cwnd 就增加到原来的 2 倍,一旦 cwnd 大于等于上次发生拥塞时 cwnd 的 1/2(即 ssthresh 的值),则停止这样指数性的增长(慢启动算法),取而代之的是每收到一个 ACK 将 cwnd 加 1 个报文段大小(拥塞避免算法) 根据上面的描述,我们可以看到 cwnd 随往返时间变化如下:
图中,在到达 ssthresh 所指定的 16 个报文段大小之前,cwnd 的设置呈指数增长,这段时间内(前四次发送)执行的是慢启动算法,在此之后,执行的则是拥塞避免算法。
慢启动算法和拥塞避免算法会让数据流突然减少,如果连续收到 3 个 ACK,则意味着某个报文段丢失,此时我们并不希望用突然减少数据流的方法来缓慢的恢复和重传,这时就会使用快速恢复算法: 1. 当发生 3 个 ACK 时,将 ssthresh 设为 cwnd 的一半,重传丢失报文段,然后将 cwnd 设置为 ssthresh + 3 个报文段大小 2. 一旦收到确认这次重传的 ACK,则将 cwnd 设置为 ssthresh,并且开始执行拥塞避免算法