当网络出现拥塞,TCP会进行数据段重传。存在两种重场景:超时重传和快速重传。
不同的重传机制使用不同的拥塞发送算法。当发生快速重传时,TCP使用快速恢复算法。
当发生快速重传时,这种情况并不及超时重传严重(只丢失了一部分数据段),因此并不需要重新进入慢启动状态。 ssthresh 和 cwnd 变化如下:
cwnd = cwnd / 2,即设置为原来的一半ssthresh = cwnd- 进入快速恢复算法
快速恢复算法操作如下:
- 拥塞窗口
cwnd = ssthresh + 3( 说明至少有 3 个分组离开了网络) - 重传丢失的数据包
- 每收到一个新的重复 ACK:
cwnd+ 1(有一个分组离开了网络)
- 收到新数据的 ACK 时:
- 说明丢失的数据已经被成功收到
- 把
cwnd直接减为ssthresh - 退出快速恢复,进入**拥塞避免算法
下图展示了 TCP Reno 的拥塞控制算法(来源于小林coding),包括慢启动、拥塞避免、快速重传和快速恢复:

存在两点疑问:
1. 为什么收到重复 ACK 时,cwnd 增加 1?#

该机制被称为窗口膨胀。
重复 ACK 的含义: 接收方能够发出重复 ACK,就证明它已经接收并缓存了一个新的乱序报文段,这意味着网络中有一个报文段(可能是乱序到达的那个,也可能是更早发送的)已经安全到达了接收端,并离开了传输中的“拥塞管道”。
窗口补偿:由于丢失的报文段未被确认,snd_una(最早未被确认的报文段)无法推进,发送窗口(rwnd 和 cwnd 的最小值)因此不能滑动。为了继续向网络注入数据流,避免“管道”变空,TCP 人为地将 cwnd 增加 ,以补偿已离开网络、但尚未被最终确认的报文段。
2. 为什么收到新的 ACK 后还要恢复到 sstresh?#
新的 ACK 的含义:收到确认新数据的 ACK,表明接收方已经收到并正确组装了之前丢失的所有报文段,因此发送窗口可以大幅向前滑动。
撤销膨胀:既然发生了快速重传,说明此时的网络或多或少还是有些拥塞的,而之前增加 cwnd 只是一种补偿机制,即补偿窗口不能滑动使得无法发送新的包。既然收到了新的 ACK 后窗口可以滑动了,那也就不需要继续补偿了,反而因为当下的拥塞状态缩小窗口。
恢复到拥塞避免:将 cwnd 设为 ssthresh 的目的,就是退出快速恢复阶段,进入拥塞避免阶段,即开始“小心”地增长,避免发生拥塞。