网工干货知识

超全学习笔记
当前位置:首页 > 干货知识

比例速率降低——TCP丢包恢复算法

更新时间:2026年03月27日   作者:spoto   标签(Tag):

TCP CUBIC正逐渐成为Linux内核中的默认传输协议。此外,用于避免拥塞的速率减半技术也正在成为Linux内核中的默认实现方式。 所以,这两者之间存在不匹配的情况。 因为“速率减半”政策对TCP Reno、Tahoe以及NewReno来说都是有益的。 对于那些在发生数据包丢失时能够将传输速率降低一半的TCP协议来说,这种做法是非常有效的。 不过,TCP Cubic能够将cwnd减少30%。与其他TCP协议相比,TCP Cubic具有这样的优势。 因此,Rate Halving和CUBIC无法作为默认设置被集成到Linux内核中。 这正是谷歌的动机所在。他们发明了一种新的算法,名为“比例速率降低算法”。 这一点在RFC 6937中有详细说明。

比例性减载:

PRR通过一些公式来计算在收到ACK信号时需要发送的新数据包的数量。为了描述这些公式,我们创造了一些新的术语。这些新术语如下:

1. sndcnt(读作“发送计数”):指的是在收到DUPACK时需要发送的数据量。这指的是在收到新的ACK时,发送方需要发送的新数据包数量。与Fast Recovery不同的是,这个数值并不等于2;而且与Rate Halving不同的是,它不会在收到两个DUP-ACK时发送1个新数据包。它可以根据某个公式计算出应该发送的新数据包的数量。

2. RecoverFS:在回收阶段开始时,管道的价值

3. 已送达:在恢复阶段期间传输的数据量。这指的是自恢复阶段开始以来,已经传输给接收方的累计数据量。这一数值相当于在恢复阶段期间接收到的DUP-ACK信号的数量。在恢复阶段之前传输了多少数据包并不重要。只有那些在数据包丢失之后才被传输的数据包才被计入统计范围内。

4. prr_out:在恢复阶段所传输的数据量。这指的是,当发送方进入恢复阶段后,它发送的新数据包的总数量。

5. 已传输的数据:通过ACK数据包所指示的数据量来表示(可以参考序列号)。不要将其与“prr_delivered”混淆。DeliveredData表示由DUP_ACK机制传输的数据包数量。如果ACK数据包的到达顺序混乱,那么DeliveredData的值可以是1或更多。

6. 限制/界限数据发送限制(由PRR算法计算得出)。这可以视为一个阈值。该数值被用于公式中,并在中间步骤中被计算出来。

7. MSS:最大段大小。这指的是一个段的典型大小。由于我们将所有术语都视为一段,因此MSS的值就为1。

比例减载模式:

比例减载法有两种模式,具体如下所示:

  1. 保守约简界限(Conservative Reduction Bound, CRB)
  2. 慢启动减少边界(Slow Start Reduction Bound,简称SSRB)[这一功能在Linux系统中默认是启用的]

我们一次只能使用一种模式,两种模式无法同时被使用。

PRR的工作原理:

当恢复阶段开始时,管道值会大于cwnd。但是,也有可能出现这种情况:由于SACK机制的原因,数据包丢失了,此时管道值就会小于cwnd。PRR算法会针对这些不同的情况采取不同的处理方式。

案例1:PRR(情况:管道 > ssthresh)

当发送方进入恢复阶段时:

pipe = 10 segments, 
RecoverFS = 10 segments
ssthresh = 7 segments,

cwnd是通过PRR来计算得出的。

cwnd = pipe + sndcnt
sndcnt = CEIL(p_d * ssthresh / RecoverFS) - p_o, {p_d=prr_delivered, p_o=prr_out}

步骤1一个DupACK的实例,其参数如下:pipe=10-1=9,p_d=1,p_o=0。(管道数量减少了1个)DupACK表示,已经交付了1个数据包。所以,p_d = 1目前还没有发送任何新的数据包,所以……p_o=0

sndcnt= CEIL(1*7/10)-0 = CEIL(0.7)-0 = 1
sender sends one new packet and pipe becomes =9+1=10 again.

步骤2有一个DupACK,其参数如下:pipe=10-1=9,p_d=2,p_o=1。(管道数量减少了1个。)DupACK表示,有一包货物已经送达了。p_d = 2 (累积的)又发送了一个新的数据包。所以,p_o=1.

sndcnt= CEIL(2*7/10)-1 = CEIL(1.4)-1= 1
sender sends one new packet and pipe becomes 9 + 1 = 10 again.

事情就是这样继续下去的。

实施:

以下是针对案例1的代码实现:

C++
// pipe > cwnd, PRR// PRR,情况1#include<bits/stdc++.h>使用命名空间标准;// 实用函数无效/无意义prr(浮点数管道,浮点数cwnd,浮点数recoverFS,浮点数ssthresh){整数i;浮点数已送达=0,prr_out=0,snd_cnt;为了(i=1;;i++){// 收到了一个DUP-ACK响应。管道-=1;// 终止条件。if(管道<ssthresh)打破/中断;// 计算cout<<“迭代”<<i<<=><<“管道 = “<<管道;已送达=i;cout<<p_d=<<已送达;snd_cnt=天花板/最高值(已送达*ssthresh/recoverFS)-prr_out;cout<<snd_cnt= “<<snd_cnt;cout<<p_o=<<prr_out<<"\n";prr_out+=snd_cnt;cwnd=管道+snd_cnt;管道=cwnd;}}// 主函数整数主要/核心(){浮点数管道,cwnd,recoverFS,ssthresh;管道=10;ssthresh=7;cwnd=管道;recoverFS=管道;prr(管道,cwnd,recoverFS,ssthresh);}

输出结果/内容
Iteration 1 => pipe= 9 p_d= 1 snd_cnt= 1 p_o= 0
Iteration 2 => pipe= 9 p_d= 2 snd_cnt= 1 p_o= 1
Iteration 3 => pipe= 9 p_d= 3 snd_cnt= 1 p_o= 2
Iteration 4 => pipe= 9 p_d= 4 snd_cnt= 0 p_o= 3
Iteration 5 => pipe= 8 p_d= 5 snd_cnt= 1 p_o= 3
Iteration 6 => pipe= 8 p_d= 6 snd_cnt= 1 p_o= 4
Iteration 7 => pipe= 8 p_d= 7 snd_cnt= 0 p_o= 5
Iteration 8 => pipe= 7 p_d= 8 snd_cnt= 1 p_o= 5
Iteration 9 => pipe= 7 p_d= 9 snd_cnt= 1 p_o= 6
Iteration 10 => pipe= 7 p_d= 10 snd_cnt= 0 p_o= 7

案例2:PRR(情况:管道长度≤ssthresh,且SSRB满足条件)

当恢复阶段开始时:

pipe = 4 segments
RecoverFS = 10 segments
ssthresh = 5 segments

cwnd是通过PRR来计算得出的。

cwnd = pipe + sndcnt
sndcnt = MIN (ssthresh - pipe, limit) and 
limit = MAX (p_d - p_o, DeliveredData) + MSS

步骤1:当有一个DupACK出现时,管道的数量会减少1个。管道长度 = 4 - 1 = 3DupACK确认,有1个数据包被成功送达了。p_d = 1目前还没有发送任何新的数据包,所以……p_o=0

limit = MAX(1-0, 1)+1 = 1+1 = 2
sndcnt= MIN(5-3, 2) = MIN(2, 2) = 2
因此,发送方会发送2个新的数据包。所以,pipe的值为3+2=5。

步骤2:当有一个 DupACK 被激活时,管道的长度就会减少 1。pipe = 5 - 1 = 4DupACK确认,有1个数据包已经送达。p_d = 2(累积值)仍然有一个新的数据包被发送了。p_o=1

limit = MAX(2-1, 1)+1 = 1+1 = 2
sndcnt= MIN(5-4, 2) = MIN(1, 2) = 1
因此,发送方会发送1个新的数据包。所以,pipe = 4 + 1 = 5。

事情就继续这样发展下去了。

实施:

以下是CASE 2的代码实现:

C++
// 使用慢启动减少连接数的方式,结合PRR技术来优化cwnd的性能#include<bits/stdc++.h>使用命名空间标准;// 实用函数无效/无意义prr_ssrb(浮点数管道,浮点数cwnd,浮点数recoverFS,浮点数ssthresh){整数i;浮点数已送达=0,prr_out=0,限制/界限,snd_cnt;为了(i=1;i<10;i++){// 收到了一个DUP-ACK响应。管道-=1;// 计算cout<<“迭代”<<i<<=> 管道 = “<<管道;已送达=i;cout<<p_d=<<已送达;cout<<p_o=<<prr_out;限制/界限=最大值(已送达-prr_out,浮点数(1))+1;cout<<限制值 = “<<限制/界限;snd_cnt=分钟(ssthresh-管道,限制/界限);cout<<snd_cnt= “<<snd_cnt<<"\n";prr_out+=snd_cnt;cwnd=管道+snd_cnt;管道=cwnd;// 终止条件。if(管道>ssthresh)打破/终止;}}// 主函数整数主要/核心(){浮点数管道,cwnd,recoverFS,ssthresh;管道=4,recoverFS=10,ssthresh=5;prr_ssrb(管道,cwnd,recoverFS,ssthresh);}

输出结果/内容
Iteration 1 => pipe= 3 p_d= 1 p_o= 0 limit= 2 snd_cnt= 2
Iteration 2 => pipe= 4 p_d= 2 p_o= 2 limit= 2 snd_cnt= 1
Iteration 3 => pipe= 4 p_d= 3 p_o= 3 limit= 2 snd_cnt= 1
Iteration 4 => pipe= 4 p_d= 4 p_o= 4 limit= 2 snd_cnt= 1
Iteration 5 => pipe= 4 p_d= 5 p_o= 5 limit= 2 snd_cnt= 1
Iteration 6 => pipe= 4 p_d= 6 p_o= 6 limit= 2 snd_cnt= 1
Iteration 7 => pipe= 4 p_d= 7 p_o= 7 limit= 2 snd_cnt= 1
Iteration 8 => pipe= 4 p_d= 8 p_o= 8 limit= 2 snd_cnt= 1
Iteration 9 => pipe= 4 p_d= 9 p_o= 9 limit= 2 snd_cnt= 1

案例3:PRR(情况:管道长度≤ssthresh,同时满足CRB条件)

在发送方进入恢复阶段时:

pipe = 4 segments
RecoverFS = 10 segments
ssthresh = 5 segments

cwnd是通过PRR来计算得出的。

cwnd = pipe + sndcnt
sndcnt = MIN (ssthresh - pipe, limit), and 
limit = p_d - p_o

步骤1:有一台DupACK到达了,管道的数量减少了1。管道长度 = 4 - 1 = 3DupACK确认,有一包货物已经送达了。p_d = 1目前还没有发送任何新的数据包,所以……p_o=0.

limit = 1-0 = 1,
sndcnt = MIN(5-3, 1) = MIN(2, 1) = 1
发送方发送了一个新的数据包,因此,管道中的数据包数量变为 3+1=4。

步骤2:有一辆DupACK到达了,管道的长度减少了1。

pipe= 4-1=3,
p_d=2, p_o=1
limit = 2-1 = 1
sndcnt = MIN(5-3, 1) = MIN(2, 1) = 1
发送方发送了一个新的数据包,因此,管道数量变为 3+1=4。

事情就继续这样发展下去了。

实施:

以下是CASE 3的代码实现:

C++
// 使用保守减少算法与PRR机制来处理<cwnd>#include<bits/stdc++.h>使用命名空间标准;// 实用函数无效/无意义prr_crb(浮点数管道,浮点数cwnd,浮点数recoverFS,浮点数ssthresh){整数i;浮点数已送达=0,prr_out=0,限制/界限,snd_cnt;为了(i=1;i<10;i++){// 收到了一个DUP-ACK响应。管道-=1;// 计算cout<<“迭代”<<i<<=> 管道 = “<<管道;已送达=i;cout<<p_d=<<已送达;cout<<p_o=<<prr_out;限制/界限=已送达-prr_out;cout<<“限制值=”<<限制/界限;snd_cnt=分钟(ssthresh-管道,限制/界限);cout<<snd_cnt= “
              马上抢免费试听资格
意向课程:*必选
姓名:*必填
联系方式:*必填
QQ:
思博SPOTO在线咨询

相关资讯

即刻预约

免费试听-咨询课程-获取免费资料