拥塞控制
流量控制是避免数据填满发送方的缓冲区。
而拥塞控制是避免发送方的数据填满整个网络。
在网络拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢包等。这个时候就会重传数据,但是一旦重传就会导致网络负担变得更重。于是会导致更大的延迟以及更多的丢包。
在⽹络出现拥堵时,如果继续发送⼤量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是⼀重传就会导致⽹络的负担更重,于是会导致更⼤的延迟以及更多的丢包,这个情况就会进⼊恶性循环被不断地放⼤….
所以,TCP 不能忽略整个网络中发⽣的事,它被设计成⼀个⽆私的协议,当⽹络发送拥塞时,TCP 会⾃我牺牲,降低发送的数据流。
拥塞窗口
拥塞窗⼝ cwnd是发送⽅维护的⼀个的状态变量,它会根据⽹络的拥塞程度动态变化的。
发送窗⼝ swnd 和接收窗⼝ rwnd 是约等于的关系,那么由于加⼊了拥塞窗⼝的概念后,此时发送窗⼝的值是 swnd = min(cwnd, rwnd)
,也就是拥塞窗⼝和接收窗⼝中的最⼩值。
拥塞窗⼝ cwnd 变化的规则:
- 只要⽹络中没有出现拥塞, cwnd 就会增⼤;
- 但⽹络中出现了拥塞, cwnd 就减少;
拥塞控制算法
慢启动
TCP建立连接之后,一开始不要发送大量数据。先探测一下网络的拥塞程度。然后逐渐增大拥塞窗口的大小。
- 如果没有发生丢包,每收到一个ACK,将拥塞窗口 cwnd 大小加1。
- 每轮次发送窗口增加一倍,呈指数增长。如果出现丢包,拥塞窗口就减半,进入拥塞避免阶段。
发包的个数呈指数级增强。
为了防止拥塞窗口无限增长,还需要设置一个慢启动阀值 ssthresh(slow start threshold)状态变量。
当到达该阈值后,即 cwnd >ssthresh 时,进入了拥塞避免算法。
拥塞避免
一般来说,慢启动阀值 ssthresh 是 65535 字节,cwnd
到达慢启动阀值后
- 每收到一个 ACK 时,cwnd = cwnd + 1/cwnd
- 当每过一个 RTT 时,cwnd = cwnd + 1
显然这是一个线性上升的算法,避免过快导致网络拥塞问题。
由指数级增长变为线性增长。
假设 ssthresh = 8 ,没收到一个ACK,增加1/8,收到8个ACK之后,cwnd+1。
拥塞发生
如果网络拥塞发生丢包时,会有两种情况:
- RTO超时重传
- 快速重传
RTO超时重传
如果发生RTO超时重传,会使用拥塞发生算法。
- 慢启动阈值 sshthresh = cwnd
- 重置cwnd=1
- 开始新一轮的慢启动过程
快速重传
发送方如果因为网络问题接收到3个连续重复的ACK时,就会触发快速重传。不必等待超时。
发⽣快速重传的拥塞发⽣算法:
- 拥塞窗口大小 cwnd = cwnd / 2
- 慢启动阀值 ssthresh = cwnd
- 进入快速恢复算法
调整拥塞窗口为一半,然后进入快速恢复算法。
快速恢复
快速重传一般和快速恢复同时使用。
快速恢复算法认为,还有三个重复ACK收到,说明网络也没有那么糟糕。不需要像RTO超时重传一样直接进入慢启动阶段。
- cwnd = sshthresh + 3(窗口扩大3个)
- 重传重复的那几个 ACK(即丢失的那几个数据包)
- 如果再收到重复的 ACK,那么 cwnd = cwnd +1
- 如果收到新数据的 ACK 后, cwnd = sshthresh。因为收到新数据的 ACK,表明恢复过程已经结束,可以再次进入了拥塞避免的算法了。
避免直接进入慢启动阶段。