重传机制
TCP常用的重传机制:
- 超时重传
- 快速重传
- SACK
- D-SACK
超时重传
两种情况会触发:数据包丢失 和 确认应答丢失
面临的问题是:重传时间可能较长
快速重传
Seq2丢失后,后续发送Seq3、4、5的时候,都是返回了ACK2,当返回三个相同ACK的时候,会在定时器过期前,重传丢失的报文段。
但是这种方法目前面临的问题是:重发报文不是重发一个还是那个往后所有的。
SACK
在TCP头部字段中加一个SACK
的东西,它可以将已收到的数据的信息发送给发送方,这样发送发就知道哪些数据收到了,哪些数据没有收到,这样就可以只重传丢失的数据。
这样就解决了快速重传中面临的问题了
D-SACK
使用场景:
- ACK丢包
接收方的ACK应答丢失了,发送方以为没发出去,就重发了,于是接收方收到重复数据后,告诉发送方真相
- 网络延迟
因为网络延迟,没有收到确认ACK。当重复传输的时候,告诉发送方,真正的原因
滑动窗口
发送窗口
接收窗口
流量控制
防止发送方发送的数据超过接收方的处理能力,避免填满接收方的缓存区,从而导致数据丢失
当窗口大小为0时,会阻止发送方给接收方发送数据,直到窗口变为非0
这时,如果接收方出现了缓存区,但是告诉发送方时候,ACK丢失了,那么发送方将一直不知道窗口大小可以变了吗?
为了解决这个问题,发送方当发现窗口为0的时候,会每隔一段时间发送一个1字节的报文去试探接收方是否还有空间
- 如果接收窗口仍然为0,则接收重新启动计时器
- 如果不为0,那接收方会得到新的窗口大小,重新开始传输数据
拥塞控制
防止发送方的数据填满网络,避免网络中的路由器和链路过载,从而导致网络拥塞和性能下降
引入一个拥塞窗口(cwnd)的概念:
- 只要网络中没有出现拥塞,
cwnd
就会增大; - 但网络中出现了拥塞,
cwnd
就减少;
发送方只要没有在规定时间内收到ACK,就会触发重传机制。那就可以理解为发生拥塞了
1. 慢启动
慢启动就是慢慢增加发送数据包的数量。也可以理解为,发送方每收到一个ACK,拥塞窗口cwnd大小就+1
有一个叫慢启动门限 ssthresh
(slow start threshold)状态变量。
- 当
cwnd
<ssthresh
时,使用慢启动算法。 - 当
cwnd
>=ssthresh
时,就会使用「拥塞避免算法」。
2. 拥塞避免算法
拥塞窗口 cwnd
「超过」慢启动门限 ssthresh
就会进入拥塞避免算法。
一般来说 ssthresh
的大小是 65535
字节。
开始进入线性增长阶段。这么一直增长后,网络就会逐渐进入阻塞的状况了
3. 拥塞发生
当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
- 超时重传
- 快速重传
当发生了「超时重传」,则就会使用拥塞发生算法。
这个时候,ssthresh 和 cwnd 的值会发生变化:
ssthresh
设为cwnd/2
,cwnd
重置为1
(是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)
但是这种方式太激进,容易产生网络卡顿。
还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh
和 cwnd
变化如下:
cwnd = cwnd/2
,也就是设置为原来的一半;ssthresh = cwnd
;- 进入快速恢复算法
4. 快速恢复
快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO
超时那么强烈。
正如前面所说,进入快速恢复之前,cwnd
和 ssthresh
已被更新了:
cwnd = cwnd/2
,也就是设置为原来的一半;ssthresh = cwnd
;
然后,进入快速恢复算法如下:
- 拥塞窗口
cwnd = ssthresh + 3
( 3 的意思是确认有 3 个数据包被收到了); - 重传丢失的数据包;
- 如果再收到重复的 ACK,那么 cwnd 增加 1;
- 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;