共计 2385 个字符,预计需要花费 6 分钟才能阅读完成。
补充 tcp 协议与滑动窗口
我们在向对端发送数据时,并不是一股脑子任意发送,因为 TCP 建立连接后,就是建立了一根管道,这跟管道上,实际上有很多的工作设备,比如路由器和交换机等等,他们都会对接收到的 TCP 包进行缓存,以便实现排序,然后发送,但是这些设备并不是只为一个 TCP 连接中转数据包,大量的网络包也许会耗尽存储空间,从而导致 TCP 连接的吞吐量急剧下降。为了避免这种情况的发送,TCP 的设计必须是一种无私的协议,它必须去探测这种网络拥塞的问题,否则我们想想,一旦出现拥塞(判断是否丢包或者是否发生重传),如果 TCP 只能做重传,那么重传数据包会使得网络上的包更多,网络的负担更重,于是导致更大的延迟以及丢更多的包,于是会进入一个恶性循环,如果网络上的所有 TCP 连接都是如此行事的话,那么马上就会形成“网络风暴”,会拖垮整个网络,这也是一个灾难。那么 TCP 就应该能够检测出来这种状况,当拥塞出现时,要做自我牺牲,就像交通阻塞一样,每一辆车都应该把路给让出来,而不是再去抢路了。这说的就是拥塞控制。那是如何控制的呢?
首先,我们得看 TCP 是如何充分利用网络的,TCP 实际上就是逐步探测这个通道的传输的最大能力,这个逐步探索就是我们要讲的慢启动算法,这个慢启动算法就是:新建立的连接不能一开始就大量发送数据包,而是应该根据网络状况,逐步地增加每次发送数据包的量。
具体的工作步骤就是:
慢启动算法:
- 发送方维护一个拥塞窗口,刚开始时,这个拥塞窗口(cwnd,congestion window)设置为 1,这个 1 代表是一个 MSS 个字节。
- 如果每收到一个 ACK,那么就指数增长这个 cwnd(2,4,8,16,32,64)等,
- 实际上不会这么一直指数级增长下去,TCP 会设置一个慢启动的阈值(ssthresh,slow start threshold,65535 个字节),当 cwnd >= ssthresh 时,进入拥塞避免阶段。
拥塞避免阶段
- 每收到一个 ACK 时,cwnd = cwnd + 1/cwnd;
- 每当每过一个 RTT 时,cwnd = cwnd + 1;
这样放缓了拥塞窗口的增长速率,避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。在这个过程中如果出现了拥塞,则进入拥塞状态。
拥塞状态 那是如何判断出现拥塞状态呢?只要出现丢包就认为进入了拥塞状态。进入拥塞状态也分两种情况:
1)等到 RTO 超时(重传超时),重传数据包。TCP 认为这种情况太糟糕,反应也很强烈:
- sshthresh = cwnd /2
- cwnd 重置为 1
- 进入慢启动过程
快速重传
2)连续收到 3 个 duplicate ACK 时,重传数据包,无须等待 RTO。此情况即为下面的快速重传。
【问题】什么情况下会出现 3 个 duplicate ACK?
TCP 在收到一个乱序的报文段时,会立即发送一个重复的 ACK,并且此 ACK 不可被延迟。
如果连续收到 3 个或 3 个以上重复的 ACK,TCP 会判定此报文段丢失,需要重新传递,而无需等待 RTO。这就叫做快速重传。
TCP Tahoe 的实现和 RTO 超时一样。TCP Reno 的实现是:
- sshthresh = cwnd
- cwnd = cwnd /2
- 进入快速恢复算法——Fast Recovery
上面我们可以看到 RTO 超时后,sshthresh 会变成 cwnd 的一半,这意味着,如果 cwnd<=sshthresh 时出现的丢包,那么 TCP 的 sshthresh 就会减了一半,然后等 cwnd 又很快地以指数级增涨爬到这个地方时,就会成慢慢的线性增涨。我们可以看到,TCP 是怎么通过这种强烈地震荡快速而小心得找到网站流量的平衡点的。
快速恢复算法
- TCP Reno
这个算法定义在 RFC5681。快速重传和快速恢复算法一般同时使用。快速恢复算法是认为,你还有 3 个 Duplicated Acks 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。注意,正如前面所说,进入 Fast Recovery 之前,cwnd 和 sshthresh 已被更新:
- sshthresh = cwnd
- cwnd = cwnd /2
然后,真正的 Fast Recovery 算法如下:
- cwnd = sshthresh + 3 * MSS(3 的意思是确认有 3 个数据包被收到了)
- 重传 Duplicated ACKs 指定的数据包
- 如果再收到 duplicated Acks,那么 cwnd = cwnd +1
- 如果收到了新的 Ack,那么,cwnd = sshthresh,代表恢复过程结束,然后就进入了拥塞避免的算法了。
如果我们仔细思考一下上面的这个算法,你就会知道,上面这个算法也有问题,那就是——它依赖于 3 个重复的 Acks。注意,3 个重复的 Acks 并不代表只丢了一个数据包,很有可能是丢了好多包。但这个算法只会重传一个,而剩下的那些包只能等到 RTO 超时,于是,进入了恶梦模式——超时一个窗口就减半一下,多个超时会超成 TCP 的传输速度呈级数下降,而且也不会触发 Fast Recovery 算法了。
- TCP New Reno
于是,1995 年,TCP New Reno(参见 RFC 6582)算法提出来:
- 当 sender 这边收到了 3 个 Duplicated Acks,进入 Fast Retransimit 模式,开发重传重复 Acks 指示的那个包。如果只有这一个包丢了,那么,重传这个包后回来的 Ack 会把整个已经被 sender 传输出去的数据 ack 回来。如果没有的话,说明有多个包丢了。我们叫这个 ACK 为 Partial ACK。
- 一旦 Sender 这边发现了 Partial ACK 出现,那么,sender 就可以推理出来有多个包被丢了,于是乎继续重传 sliding window 里未被 ack 的第一个包。直到再也收不到了 Partial Ack,才真正结束 Fast Recovery 这个过程。
我们可以看到,这个“Fast Recovery 的变更”是一个非常激进的玩法,他同时延长了 Fast Retransmit 和 Fast Recovery 的过程。