万字详文:TCP 拥塞控制详解( 七 )


文章插图
 
以此来将目标窗口收敛于 ssthresh 。刚进入快速恢复的时候的时候,窗口尚未下降,在收敛结束之前,下面的不等式是成立的:

万字详文:TCP 拥塞控制详解

文章插图
 
这意味着在收敛结束前,我们可以多发送 extra 这么多的数据包 。
降窗流程 根据上述原理可以得到 prr 算法降窗流程如下:
1.收到多次(默认为 3)重复 ACK,根据回调函数更新 ssthresh (reno 算法为 cwnd/2),窗口维持不变;
2.每收到 ACK(不管是否重复),按上述算法计算 Extra; 3. 新窗口值取 cwnd = in_flight + Extra; 4. 直到退出快速恢复,cwnd = ssthresh;
优点:在快速恢复期间,取消了窗口陡降过程,可以更平滑发送数据,且降窗速率与 PIPE 容量相关 。缺点:不利于在拥塞恢复到正常时突发流量的发送 。
5.5 记分板算法记分板算法是为了统计网络中正在传输的包数量,即tcp_packets_in_flight 。只有当 cwnd > tcp_packets_in_flight 时,TCP 才允许发送重传包或者新数据包到网络中 。tcp_packets_in_flight和packets_out, sacked_out, retrans_out,lost_out有关 。其中packets_out表示发出去的包数量,sacked_out为sack的包数量,retrans_out为重传的包数量,lost_out为loss的包数量,这里的loss包含rto,FR和RACK等机制判断出来的丢失包 。
万字详文:TCP 拥塞控制详解

文章插图
 
为了可以正确统计这些数据,内核给每个 tcp 包(tcp_skb_cb)添加了sacked字段标记该数据包当前的状态 。
__u8sacked;/* State flags for SACK.*/#define TCPCB_SACKED_ACKED0x01/* SKB ACK'd by a SACK block*/#define TCPCB_SACKED_RETRANS0x02/* SKB retransmitted*/#define TCPCB_LOST0x04/* SKB is lost*/#define TCPCB_TAGBITS0x07/* All tag bits*/#define TCPCB_REPAIRED0x10/* SKB repaired (no skb_mstamp_ns)*/#define TCPCB_EVER_RETRANS0x80/* Ever retransmitted frame */#define TCPCB_RETRANS(TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS|TCPCB_REPAIRED)需要在意的有TCPCB_SACKED_ACKED(被 SACK 块 ACK'd),TCPCB_SACKED_RETRANS(重传),TCPCB_LOST(丢包),其状态转换图如下:
万字详文:TCP 拥塞控制详解

文章插图
sack 处理记分板
记分板状态转换逻辑如下:
  1. 首先判定丢包,打L tag,lost_out++,即 L
  2. 如果需要重传,打Rtag,retrans_out++,即 L|R
  3. 如果再次丢包,取消Rtag,retrans_out–,lost_out 维持不变,go to step2,此时标记位为 L
  4. 当 SACKED 时,取消L|R,retrans_out–,lost_out–,此时为 S
  5. 当 snd_una 向右更新时,packet_out–
5.6 拥塞窗口校验在 [RFC2861] 中,区分了 TCP 连接数据传输的三种状态:
  • network-limited:TCP 的数据传输受限于拥塞窗口而不能发送更多的数据 。
  • application-limited:TCP 的数据传输速率受限于应用层的数据写入速率,并没有到达拥塞窗口上限 。
  • idle:发送端没有额外的数据等待发送,当数据发送间隔超过一个 RTO 的时候就认为是 ilde 态 。
cwnd 代表了对网络拥塞状态的一个评估,拥塞控制要根据 ACK 来更新 cwnd 的前提条件是,当前的数据发送速率真实的反映了 cwnd 的状况,也就是说当前传输状态是 network-limited 。假如 tcp 隔了很长时间没有发送数据包,即进入 idle,那么当前真实的网络拥塞状态很可能就会与 cwnd 反映的网络状况有差距 。另外在 application-limited 的场景下,受限数据的 ACK 报文还可能把 cwnd 增长到一个异常大的值,显然是不合理的 。基于上面提到的两个问题,[RFC2861]引入了拥塞窗口校验(CWV,Congestion Window Validation)算法 。
算法如下:当需要发送新数据时,首先看距离上次发送操作是否超过一个 RTO,如果超过,则 1. 更新 sshthresh 值,设为 max(ssthresh, (3/4) * cwnd) 。2.每经过一个空闲 RTT 时间,cwnd 值减半,但不小于 1 SMSS 。
对于应用受限阶段(非空闲阶段),执行相似的操作:1. 已使用的窗口大小记为W used。2. 更新 ssthresh 值,设为 max(ssthresh, (3/4) * cwnd) 。
  1. cwnd 设为 cwnd 和W used的平均值 。
上述操作均减小了 cwnd,但 ssthresh 维护了 cwnd 的先前值 。避免空闲阶段可能发生的大数据量注入,可以减轻对有限的路由缓存的压力,从而减少丢包情况的产生 。注意 CWV 减小了 cwnd 值,但没有减小 ssthresh,因此采用这种算法的通常结果是,在长时间发送暂停后,发送方会进入慢启动阶段 。Linux TCP 实现了 CWV 算法并默认启用 。
六、常见的拥塞算法6.1 New Reno 算法New Reno 算法包含第五节中介绍的慢启动算法、拥塞避免算法、快速重传算法和 prr 算法 。在此就不赘述 。


推荐阅读