引言
有了流量控制
为什么还要有拥塞控制
?
流量控制是避免 发送方 的数据填满 接收方 的缓存。
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大…. 所以,TCP 不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。
拥塞控制 是避免 发送方 的数据填满整个网络。 在 发送方 调节所要发送数据的量,定义了 拥塞窗口
拥塞窗口和发送窗口有什么区别?
拥塞窗口 cwnd
是发送方维护的一个 的状态变量,会根据网络的拥塞程度动态变化的。 前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,加入拥塞窗口概念后,发送窗口的值是swnd = min(cwnd, rwnd)
,即拥塞窗口和接收窗口中的最小值。
拥塞窗口 cwnd
变化的规则:只要网络中没有出现拥塞, cwnd
就会增大; 但网络中出现了拥塞, cwnd
就减少;
怎么知道网络出现了阻塞?
只要 发送方 没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就认为网络 出现了拥塞。
拥塞控制主要是四个算法:
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
慢启动
TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据 包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?
慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,就拥塞窗口 cwnd 的大小就会加 1。
假定拥塞窗口 cwnd
和发送窗口 swnd
相等,下面举个栗子:
- 连接建立完成后,一开始初始化 cwnd = 1 ,表示可以传一个 MSS 大小的数据。
- 当收到 1 个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个
- 当收到 2 个的 ACK 确认应答后,cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个
- 当收到 4 个的 ACK 确认到来的时候,4 个确认 cwnd 增加 4,于是就可以 比之前多发 4 个,所以这一次能够发送 8 个
可以看出慢启动算法,发包的个数是指数性的增长。
那慢启动涨到什么时候是个头呢?
有一个叫慢启动门限 ssthresh (slow start threshold
)状态变量
- 当 cwnd < ssthresh 时,使用慢启动算法
- 当 cwnd >= ssthresh 时,就会使用 拥塞避免算法
拥塞避免
当拥塞窗口 cwnd >= ssthresh
慢启动门限 就会进入拥塞避免算法
一般来说 ssthresh 的大小是 65535 字节 = 2^16 - 1
那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd
接上前面的慢启动的例子,现假定 ssthresh
为 8 :
- 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次 能够发送 9 个 MSS 大小的数据,变成了线性增长
拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶 段,但是增长速度缓慢了一些。 就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失 的数据包进行重传。 当触发了重传机制,也就进入了 拥塞发生算法
拥塞发生
当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
- 超时重传
- 快速重传
发生超时重传则就会使用拥塞发生算法。 这个时候,sshresh 和 cwnd 的值会发生变化:
ssthresh
设为 cwnd/2- cwnd 重置为 1
接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦超时重传 ,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。
还有更好的方式 快速重传算法:
当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。 TCP 认为这种情况不严重,因为大部分没丢,只丢了小部分,则 ssthresh 和 cwnd 变化如下:
cwnd = cwnd/2
设置为原来的一半;ssthresh = cwnd
进入快速恢复算法
快速恢复
快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也 不那么糟糕,所以没有必要像 RTO 超时那么强烈。
进入快速恢复之前, cwnd 和 ssthresh 已被更新了:
- cwnd = cwnd/2 ,设置为原来的一半
- ssthresh = cwnd
然后进入快速恢复算法如下:
- 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了)
- 重传丢失的数据包
- 如果再收到重复的 ACK,那么 cwnd 增加 1
- 如果收到新数据的 ACK 后,设置 cwnd 为 ssthresh,接着就进入了拥塞避免算法
也就是没有像 超时重传 一夜回到解放前,而是还在比较高的值,后续呈线性增长