Skip to main content

6. 算法 (Algorithm)

6.1. 初始化步骤 (Initialization Steps)

在由拥塞控制算法启动的拥塞控制响应阶段开始时,使用PRR的数据发送方必须初始化PRR状态。

拥塞控制响应阶段开始的时机完全由拥塞控制算法决定,例如,可能对应于快速恢复阶段的开始,或在快速恢复已经进行后检测到丢失的重传或丢失的原始传输时每轮进行一次降低。

PRR初始化允许拥塞控制算法CongCtrlAlg()将ssthresh设置为FlightSize/2以外的值(例如,包括CUBIC[RFC9438])。

PRR初始化的一个关键步骤是计算恢复飞行大小(Recovery Flight Size, RecoverFS),即数据发送方估计在PRR阶段过程中可能交付的字节数。这可以被认为是阶段开始时以下值的总和:inflight、触发恢复的ACK中累积确认的字节、触发恢复的ACK中SACKed的字节,以及SND.UNA和SND.NXT之间已标记为丢失的字节。RecoverFS包括丢失,因为丢失是使用启发式方法标记的,因此在恢复期间,一些先前标记为丢失的数据包最终可能会被交付(无需重传)。PRR使用RecoverFS来计算平滑的发送速率。进入快速恢复后,PRR初始化RecoverFS,并且RecoverFS在给定的快速恢复阶段期间保持不变。

完整的PRR算法初始化步骤序列如下:

ssthresh = CongCtrlAlg()  // 恢复中的目标飞行大小
prr_delivered = 0 // 恢复中交付的总字节数
prr_out = 0 // 恢复中发送的总字节数
RecoverFS = SND.NXT - SND.UNA // 进入恢复前SACKed的字节

// 进入恢复前SACKed的字节将不会在恢复期间
// 标记为已交付:
RecoverFS -= (记分板中SACKed的字节)

// 包括选择性ACKed字节的(常见)情况:
RecoverFS += (新SACKed的字节)

// 包括累积确认字节的(罕见)情况:
RecoverFS += (新累积确认的字节)

6.2. 每ACK步骤 (Per-ACK Steps)

在快速恢复开始或期间的每个ACK上,不包括结束PRR阶段的ACK,PRR执行以下步骤。

首先,发送方计算DeliveredData,即数据发送方对当前ACK指示自先前接收到的ACK以来已交付给接收方的总字节数的最佳估计。使用SACK,DeliveredData可以精确计算为SND.UNA的变化,加上记分板中标记为SACKed的数据量的有符号变化。因此,在特殊情况下,当ACK之前或之后记分板中没有SACKed序列范围时,DeliveredData是SND.UNA的变化。

在没有SACK的恢复中,DeliveredData在每个接收到的重复ACK上估计为1 SMSS(即SND.UNA未改变)。当SND.UNA前进时(即完全或部分ACK),DeliveredData是SND.UNA的变化,减去每个前面重复ACK的1 SMSS。注意,在没有SACK的情况下,返回多余重复ACK的行为不当的接收方(如[Savage99]中所述)可能试图人为地夸大DeliveredData。作为缓解措施,如果不使用SACK,则当PRR阶段中交付的总字节数超过进入恢复时估计的未完成数据(RecoverFS)时,PRR不允许增加DeliveredData。

接下来,发送方计算inflight,即数据发送方对网络中飞行的字节数的最佳估计。要计算inflight,启用SACK并使用[RFC6675]丢包检测的连接可以使用[RFC6675]中指定的"pipe"算法。使用RACK-TLP丢包检测[RFC8985]或其他丢包检测算法的启用SACK的连接必须通过从SND.NXT - SND.UNA开始,减去记分板中SACKed的字节,减去记分板中标记为丢失的字节,并添加自上次标记为丢失以来已重传的记分板中的字节来计算inflight。

对于未启用SACK的连接,发送方必须减去:min(RecoverFS, 快速恢复阶段中每个前面重复ACK的1 SMSS);与RecoverFS的min()是为了防止行为不当的接收方[Savage99],而不是减去SACK记分板中SACKed的字节。

接下来,发送方计算SafeACK,一个本地布尔变量,指示当前ACK报告了良好进展。SafeACK仅在ACK累积确认了新数据且ACK未指示进一步丢失时为真。例如,触发"救援"重传的ACK([RFC6675]第4节,NextSeg()条件4)可能指示进一步丢失。这两个条件都表明恢复正在取得良好进展,如果合适,发送方可以更激进地发送,增加inflight。

最后,发送方使用DeliveredData、inflight、SafeACK和其他PRR状态来计算SndCnt,一个本地变量,指示响应每个ACK应发送多少字节,然后使用SndCnt更新cwnd。

完整的每ACK PRR算法步骤序列如下:

if (DeliveredData is 0)
Return

prr_delivered += DeliveredData
inflight = (估计的飞行中数据量)
SafeACK = (SND.UNA前进且未指示进一步丢失)

if (inflight > ssthresh) {
// 比例速率降低
// 这使用整数除法,向上舍入:
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
out = DIV_ROUND_UP(prr_delivered * ssthresh, RecoverFS)
SndCnt = out - prr_out
} else {
// 默认使用PRR-CRB
SndCnt = MAX(prr_delivered - prr_out, DeliveredData)
if (SafeACK) {
// 当恢复进展良好时使用PRR-SSRB
SndCnt += SMSS
}
// 尝试追赶,在允许的范围内
SndCnt = MIN(ssthresh - inflight, SndCnt)
}

if (prr_out is 0 AND SndCnt is 0) {
// 进入恢复时强制快速重传
SndCnt = SMSS
}

cwnd = inflight + SndCnt

在发送方计算SndCnt并使用它更新cwnd后,发送方传输更多数据。注意,发送哪些数据的决定(例如,重传缺失数据或发送更多新数据)超出了本文档的范围。

6.3. 每次传输步骤 (Per-Transmit Steps)

在任何数据传输或重传时,PRR执行以下操作:

prr_out += (发送的数据)

6.4. 完成步骤 (Completion Steps)

PRR阶段在完成快速恢复或在由于新的拥塞控制响应阶段而启动新PRR阶段之前结束。

完成PRR阶段时,PRR执行以下操作:

cwnd = ssthresh

注意,这个将cwnd设置为ssthresh的步骤在某些情况下可能允许段的背靠背突发进入网络。

建议实现使用分组(pacing)来减少数据流量的突发性。此建议与当前缓解突发的实践一致(例如,[PACING]),包括在从空闲重启后对传输突发进行分组。