一、前言
-
通过上面五遍文章的学习,笔者对
google quiche
的代码框架,以及quic
的基本原理已经有了较为清晰的认识,本文开始笔者将分析google quiche
项目中ACK
的实现原理,为后续分析google quiche
丢包重传的实现已经拥塞控制的实现做铺垫。 -
本文将从服务端和客户端两方面入手进行分析,着重分析
ACK
的发送时机、ACK FRAME
的定义和实现、AckFrame
的处理流程、以及相关模块的核心逻辑实现等。
二、认识AckFrame
2.1)AckFrame协议认识
- 本节通过抓包问价并配合RFC9000先认识AckFrame中的内容,首先我们我们先看一个抓包文件
001.png
ACK frames are formatted as shown in Figure 25.
ACK Frame {
Type (i) = 0x02..0x03,
Largest Acknowledged (i),
ACK Delay (i),
ACK Range Count (i),
First ACK Range (i),
ACK Range (..) ...,
[ECN Counts (..)],
}
-
Largest Acknowledged:一个可变长度的整数,表示对端在生成
ACK Frame
帧之前接收到的最大数据包号。 -
ACK Delay:以微秒为单位对确认延迟进行编码的可变长度整数; 参考 Section 13.2.5. 通过将字段中的值乘以2的
ack_delay_exponent
传输参数的幂来解码; 参考 Section 18.2. 什么意思呢?AckFrame
发送端在发送该帧的时候,首先是会计算当前收到的最大包,经历多长时间开始发送AckFrame
,单位一般为微秒,这里假设定义delay_us = (now - receive_time).us
,只不过这里的ACK Delay=delay_us >>ack_delay_exponent
,其中google_quiche
中定义的这个ack_delay_exponent=3
,而对于AckFrame
接收的一方来说需要通过ACK Delay << ack_delay_exponent
解出AckFrame
在对端的处理延迟时间。 -
ACK Range Count:一个可变长度的整数,这个字段代表的是对端在本次
Ack
的时候收到的所有包号,分成了多少段-1,为什么会出现分段?假设丢包了就会出现分段了,按照图(1)举个例子,在这个抓包中,当前最大收到的包为43,其中丢了30和42号包,所以分成了3个段。 -
First ACK Range:一个可变长度的整数,表示在最大已确认数之前被确认的连续报文数。也就是说,该范围内确认的最小数据包由“最大确认”字段减去“第一个ACK范围”的值确定。
-
ACK Ranges:包含未被确认(Gap)和已确认(ACK范围)的数据包的附加范围; 看 Section 19.3.1。
-
ECN Counts:The three ECN counts; see Section 19.3.2.
ACK Range {
Gap (i),
ACK Range Length (i),
}
-
要怎么认识这个
ACK Range
,在google quiche
中维护了一个连续分范围的Set
集合,当接收包的时候,若出现了丢包,那么这个Set
就会分段存储,并且是使用左闭右([a,b)
)开的方法,其中a
和b
之间一定连续,但是b
是没收到的,按照图(1)的抓包文件来看,自上次Ack
后,首先收到5号包,然后一直连续收到29号包,再然后就收到了31号包,中间丢了30号包,接着一直连续收包到41号包。大致存储成如下布局:[5,30) [31,42) [43,44) 左闭右
-
其中
[5,30)
代表的是ACK Range[2]
,[31,42)
对应的是ACK Range[1]
,而[43,44)
对应的是ACK Range[0]
也就是First ACK Range
,注意是逆序的。 -
而每一个
ACK Range
的长度(ACK Range Length
)字段就是这个左闭右开区间中实际收到的包的个数减去1,也可以说成元素个数减1。 -
再看
First ACK Range
字段其实就是对应ACK Range[0]
的信息,只不过是不包含gap
信息,因为这个gap
可以由前面的range
算出来,这里得到的就是第0
个range
真实接收到的包的个数减去1。 -
gap
指的是当前区间的开值和后面区间的闭值相减然后减去1,如上对于ACK Range[1](gap)
=43 - 42 - 1
,ACK Range[2](gap)
=31 - 30 - 1
,其实就可以理解成每个区间之间丢失包的个数。
2.2)QuicAckFrame数据结构定义
struct QUIC_EXPORT_PRIVATE QuicAckFrame {
QuicAckFrame();
QuicAckFrame(const QuicAckFrame& other);
~QuicAckFrame();
void Clear();
friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
std::ostream& os, const QuicAckFrame& ack_frame);
// The highest packet number we've observed from the peer. When |packets| is
// not empty, it should always be equal to packets.Max(). The |LargestAcked|
// function ensures this invariant in debug mode.
QuicPacketNumber largest_acked;
// Time elapsed since largest_observed() was received until this Ack frame was
// sent.
QuicTime::Delta ack_delay_time = QuicTime::Delta::Infinite();
// Vector of <packet_number, time> for when packets arrived.
// For IETF versions, packet numbers and timestamps in this vector are both in
// ascending orders. Packets received out of order are not saved here.
PacketTimeVector received_packet_times;
// Set of packets.
PacketNumberQueue packets;
// ECN counters.
absl::optional<QuicEcnCounts> ecn_counters;
};
- 本节不做中文翻译,英文注释已经很详细
三、接收端记录接收包,并计算AckFrame的发送时间
- 接收端收到每一个数据包后会对包进行记录,并且会尝试更新超时时间,
QuicConnection
模块在发送数据或者Ack
定时超时的时候会获取该超时,如果已经超时,则会在发送数据的时候聚合一个AckFrame
进行发送,而对于只接收数据不发送数据的接收端来说,AckFrame
的发送就依赖于Ack
定时器进行定时发送。 - 首先认识一下相关的数据结构如下:
class QUIC_EXPORT_PRIVATE QuicConnection
: .... {
....
UberReceivedPacketManager uber_received_packet_manager_;
};
-
QuicConnection
模块中有一个UberReceivedPacketManager
类型的成员变量,该类用于收包管理,该类依赖QuicReceivedPacketManager
模块完成对收包的管理,对外提供RecordPacketReceived()、GetUpdatedAckFrame()、MaybeUpdateAckTimeout()、ResetAckStates()
等核心Api
来完成接收端发送AckFrame
的核心业务逻辑。 -
其中当接收到一个包之后,大致的处理流程如下:
002.png -
根据图(2)的流程不难看出,当收到一个包后首先是解析、解析完后通过步骤#2对包进行记录,记录完后尝试更新
Ack
超时时间,接下来本节着重分析这两个核心函数的实现:
3.1) RecordPacketReceived函数分析
-
UberReceivedPacketManager
模块的RecordPacketReceived()
API用于完成对包的信息记录,其具体实现如下:
void UberReceivedPacketManager::RecordPacketReceived(
EncryptionLevel decrypted_packet_level, const QuicPacketHeader& header,
QuicTime receipt_time, QuicEcnCodepoint ecn_codepoint) {
if (!supports_multiple_packet_number_spaces_) {
received_packet_managers_[0].RecordPacketReceived(header, receipt_time,
ecn_codepoint);
return;
}
received_packet_managers_[QuicUtils::GetPacketNumberSpace(
decrypted_packet_level)]
.RecordPacketReceived(header, receipt_time, ecn_codepoint);
}
void QuicReceivedPacketManager::RecordPacketReceived(
const QuicPacketHeader& header, QuicTime receipt_time,
const QuicEcnCodepoint ecn) {
const QuicPacketNumber packet_number = header.packet_number;
QUICHE_DCHECK(IsAwaitingPacket(packet_number))
<< " packet_number:" << packet_number;
// 1) 判断是否丢包,其中成员ack_frame_为QuicAckFrame类型
was_last_packet_missing_ = IsMissing(packet_number);
if (!ack_frame_updated_) {
// 每次acked之后,的下一次ack清理过完接收信息
ack_frame_.received_packet_times.clear();
}
ack_frame_updated_ = true;
// Whether |packet_number| is received out of order.
// 2) 乱序处理,LargestAcked代表的是截止当前收到的最大包号,如果已经收到的最大包号比当前收到的包号要大,那就是乱序了
bool packet_reordered = false;
if (LargestAcked(ack_frame_).IsInitialized() &&
LargestAcked(ack_frame_) > packet_number) {
// Record how out of order stats.
// 更新统计信息
packet_reordered = true;
++stats_->packets_reordered;
stats_->max_sequence_reordering =
std::max(stats_->max_sequence_reordering,
LargestAcked(ack_frame_) - packet_number);
int64_t reordering_time_us =
(receipt_time - time_largest_observed_).ToMicroseconds();
stats_->max_time_reordering_us =
std::max(stats_->max_time_reordering_us, reordering_time_us);
}
if (!LargestAcked(ack_frame_).IsInitialized() ||
packet_number > LargestAcked(ack_frame_)) {
ack_frame_.largest_acked = packet_number;
time_largest_observed_ = receipt_time;
}
// 3) 将当前收到的包号插入到packets队列(PacketNumberQueue),该队列是由一个可分段范围的Set来进行实现,其中每一个范围都是可连续的收到的包如
// [0,100) [101,200) 表示0~99已经收到,101~199已经收到,100号丢失,如果完全没有丢失,那么就是[0 200],当中间出现乱序之后,假设又收到了已乱序的包
// ack_frame_.packets可以将两个分段合并成一个分段,如上,一开始丢了100号包,假设在收到101号包之后又收到了100号包,那么这个队列的就变成[0,200)了
ack_frame_.packets.Add(packet_number);
//4) 尝试丢弃最小段范围的包,默认初始化的时候ack_frame_.packets队列会设置一个最大范围限制,默认是255,也就是最多可容纳255个范围连续的包,超过了则会将
// 最小的范围进行丢弃
MaybeTrimAckRanges();
//5) 对接收时间戳进行记录,这里需要双向协商,如果使用时间戳那么AckFrame中会携带接收端的接收时间戳信息
if (save_timestamps_) {
// The timestamp format only handles packets in time order.
if (save_timestamps_for_in_order_packets_ && packet_reordered) {//乱序了不记录
QUIC_DLOG(WARNING) << "Not saving receive timestamp for packet "
<< packet_number;
} else if (!ack_frame_.received_packet_times.empty() &&
ack_frame_.received_packet_times.back().second > receipt_time) {//时间回滚了不处理
QUIC_LOG(WARNING)
<< "Receive time went backwards from: "
<< ack_frame_.received_packet_times.back().second.ToDebuggingValue()
<< " to " << receipt_time.ToDebuggingValue();
} else {//以packet_number为key,接收时间戳为value记录到received_packet_times当中
ack_frame_.received_packet_times.push_back(
std::make_pair(packet_number, receipt_time));
}
}
//6) 如果某个路由有ECN支持,这里记录ECN信息
if (GetQuicRestartFlag(quic_receive_ecn2) && ecn != ECN_NOT_ECT) {
QUIC_RESTART_FLAG_COUNT_N(quic_receive_ecn2, 1, 2);
if (!ack_frame_.ecn_counters.has_value()) {
ack_frame_.ecn_counters = QuicEcnCounts();
}
switch (ecn) {
case ECN_NOT_ECT:
QUICHE_NOTREACHED();
break; // It's impossible to get here, but the compiler complains.
case ECN_ECT0:
ack_frame_.ecn_counters->ect0++;
break;
case ECN_ECT1:
ack_frame_.ecn_counters->ect1++;
break;
case ECN_CE:
ack_frame_.ecn_counters->ce++;
break;
}
}
//7) 更新收到的最小包号,因为有可能乱序,所以这里需要更新
if (least_received_packet_number_.IsInitialized()) {
least_received_packet_number_ =
std::min(least_received_packet_number_, packet_number);
} else {
least_received_packet_number_ = packet_number;
}
}
-
RecordPacketReceived
函数的核心作用是根据收到包的PacketNumber
、时间戳、ECN等信息,将其保存到QuicReceivedPacketManager
模块的成员变量ack_frame_
当中。
3.2) MaybeUpdateAckTimeout函数计算AckFrame发送时间
- 按照图(2)的流程接收端对收到的包的处理分成很多个流程,当解析出实际的
QuicFrame
以及包处理完成后,会调用QuicConnection::MaybeUpdateAckTimeout()
函数,而该函数的实现主要是bypass调用如下:
void QuicReceivedPacketManager::MaybeUpdateAckTimeout(
bool should_last_packet_instigate_acks,/*默认都是true*/
QuicPacketNumber last_received_packet_number,
QuicTime last_packet_receipt_time, QuicTime now,
const RttStats* rtt_stats) {
if (!ack_frame_updated_) {//由于是单线程,按照流程基本不可能发生
// ACK frame has not been updated, nothing to do.
return;
}
// 1) 乱序处理,last_sent_largest_acked_表示,上一次已经发送的QuicAckFrame中已应答的最大包序号,这里假设上次Ack的时候,最大包序号是20,
// 然后19号丢了而此时又收到了19号包,那么Ack的超时时间设置成Now,也就是应该立即Ack
if (!ignore_order_ && was_last_packet_missing_ &&
last_sent_largest_acked_.IsInitialized() &&
last_received_packet_number < last_sent_largest_acked_) {
// Only ack immediately if an ACK frame was sent with a larger largest acked
// than the newly received packet number.
ack_timeout_ = now;
return;
}
...
// 2) 统计自上次发送ack自当前收到的包的数量+1
++num_retransmittable_packets_received_since_last_ack_sent_;
/* 3) 尝试更新AckFrame的发送频率
* 3.1) 如果当前收到了AckFrequencyFrame,此处不更新
* 3.2) 如果当前收到的包号 < 当前未acked的包号least_received_packet_number_ + min_received_before_ack_decimation_(100)
* 也就是100个包以内不更新AckFrame的频率
* 3.3) 否则 unlimited_ack_decimation_ 默认为false,表示不限制收到多少个包后进行ack,
* 可通过协商QuicTag kAKDU = TAG('A', 'K', 'D', 'U')来配置该值为true
* ack_frequency_ = unlimited_ack_decimation_
* ? std::numeric_limits<size_t>::max()
* : kMaxRetransmittablePacketsBeforeAck;//默认值是10个包
* 默认10个包需要发送Ack?
*/
MaybeUpdateAckFrequency(last_received_packet_number);
// 4) 基于#3的计算基础会得到一个ack_frequency_,如果截止上一次发送AckFrame到当前收到了大于ack_frequency_(这里算出来是10个或者2^16 - 1)个包
// 则设置ack_timeout_为Now,表示可以立即发送AckFrame。
if (num_retransmittable_packets_received_since_last_ack_sent_ >=
ack_frequency_) {
ack_timeout_ = now;
return;
}
/* 5) 基于丢包的AckTime决策
* 5.1) 当设置one_immediate_ack_为true,也就是配置QuicTag k1ACK = TAG('1', 'A', 'C', 'K')的情况下,只要发生乱序或者丢包,则立即响应发送Ack
* 5.2) 如5.1 未配置,也就是默认情况如果出现丢包,并且出现丢包后收到的连续包的数量小于4以内则立即进行Ack
*/
if (!ignore_order_ && HasNewMissingPackets()) {
ack_timeout_ = now;
return;
}
//6) 没有出现丢包,则在当前收包时间的基础上加上一个最大AckDelay延迟(基于RTT)
const QuicTime updated_ack_time = std::max(
now, std::min(last_packet_receipt_time, now) +
GetMaxAckDelay(last_received_packet_number, *rtt_stats));
if (!ack_timeout_.IsInitialized() || ack_timeout_ > updated_ack_time) {
ack_timeout_ = updated_ack_time;
}
}
-
AckFrequencyFrame
允许发送方向接收方传输一个帧,其中包含了关于ACK包
发送频率的参数。通过发送AckFrequencyFrame
,发送方可以调整ACK
包的发送频率,以适应当前网络条件和性能需求。 - 通过上述代码分析可以得出
MaybeUpdateAckTimeout
函数的核心作用是对AckFrame
的发送时机进行决策,一共分成四种决策。 - 决策1)乱序,并且在上次发送完
AckFrame
后又收到了乱序包,则应该立即发送AckFrame
。 - 决策2)基于已收包数量,默认阈值为10个包,该策略可通过配置进行忽略,当每收到10个包的时候应该进行
Ack
。 - 决策3)基于丢包,看上去是只要出现了丢包就会立即发送
AckFrame
,代码如下:
bool QuicReceivedPacketManager::HasNewMissingPackets() const {
if (one_immediate_ack_) {
return HasMissingPackets() && ack_frame_.packets.LastIntervalLength() == 1;
}
return HasMissingPackets() &&
ack_frame_.packets.LastIntervalLength() <= kMaxPacketsAfterNewMissing;
}
- 其中
one_immediate_ack_
表示当只要出现丢包的时候就进行Ack
,但是默认为false,而后面的逻辑看上去有点不大好理解,这里主要的原因是quic
支持聚合包,而按照图(2)的流程5是在QuicConnection::OnPacketComplete()
函数中回调的,这个地方假设出现了聚合包,那么就会存在一个UDP包中包含了好几个QuicIetf
包,所以这里的ack_frame_.packets.LastIntervalLength()
有可能就会大于kMaxPacketsAfterNewMissing
。 - 意思就是假设一开始收到了10号包,然后丢了11号包,然后又连续收到了12~20号(聚合)包,这个时候就不需要立即发送
AckFrame
了,有可能是乱序。 - 决策4)基于当前收包时间+上一个最大延迟,其中最大延迟
GetMaxAckDelay()
的计算如下:
QuicTime::Delta QuicReceivedPacketManager::GetMaxAckDelay(
QuicPacketNumber last_received_packet_number,
const RttStats& rtt_stats) const {
if (AckFrequencyFrameReceived() ||
last_received_packet_number < PeerFirstSendingPacketNumber() +
min_received_before_ack_decimation_) {//100个包以内返回local_max_ack_delay_
return local_max_ack_delay_;//默认25Mslow-bandwidth (< ~384 kbps),
}
// Wait for the minimum of the ack decimation delay or the delayed ack time
// before sending an ack.
// ack_decimation_delay_默认为0.25,可以通过QuicTag kAKD3 = TAG('A', 'K', 'D', '3')配置成0.125倍RTT
QuicTime::Delta ack_delay = std::min(
local_max_ack_delay_, rtt_stats.min_rtt() * ack_decimation_delay_);
return std::max(ack_delay, kAlarmGranularity);
}
- 该决策,首先判断是不是100个包以内,如果是则最大延迟为
25Ms
。如超过100个包,则为25Ms
和当前0.25*最小RTT
取最小值。
四、接收端AckFrame发送时机介绍
- 在第三节中有分析了
AckFrame
的超时时间,也就满足发送AckFrame
的条件,本节开始从代码层面分析作为接收端,AckFrame
的发送时机有哪些。
4.1)场景1:发送握手数据的时候发送AckFrame
- 首先我们看握手阶段,当服务端收到客户端的
Initial
后,服务端在回Intial
包的时候就会发送AckFrame
,同理客户端收到服务端的Initial
+handshake
包后也会回复AckFrame
。
003.png -
其中图(3)无论是客户端还是服务端,其代码处理逻辑基本一致,如下:
004.png - 该流程说明在发送握手包的时候是有机会发送
AckFrame
的,但是要注意图(4)中的#1,如果第一步通过GetAckTimeout()
返回的超时时间已经被初始化,才会有步骤2和步骤3,否则是不会发送的。 - 很显然对于握手阶段服务端收到握手包后会立即处理,从而会触发第三节提到的
AckFrame
发送超时时间的计算,也就是此时的#1是一定会返回有值的,透过代码来看可能会更直观。
const QuicFrames QuicConnection::MaybeBundleAckOpportunistically() {
/* 1) 如果支持发送AckFrameFrequency 帧(ack 频率控制帧),并且下一个要发送的pkgNumber大于100+首次发送的pkgNumber
* 也就是每100个包发送一次ack频率控制帧?
*/
if (!ack_frequency_sent_ && sent_packet_manager_.CanSendAckFrequency()) {
if (packet_creator_.NextSendingPacketNumber() >=
FirstSendingPacketNumber() + kMinReceivedBeforeAckDecimation) {
QUIC_RELOADABLE_FLAG_COUNT_N(quic_can_send_ack_frequency, 3, 3);
ack_frequency_sent_ = true;
auto frame = sent_packet_manager_.GetUpdatedAckFrequencyFrame();
visitor_->SendAckFrequency(frame);
}
}
// 2) 看AckTimeOut时间是否已经被赋值,按照代码实现来看,对于及发送又接收的端来说,
// 这里基本是在发送数据之前,如果有收到还未确认的包,理论上每次发送数据的时候都会发送AckFrame
QuicFrames frames;
const bool has_pending_ack =
uber_received_packet_manager_
.GetAckTimeout(QuicUtils::GetPacketNumberSpace(encryption_level_))
.IsInitialized();
if (!has_pending_ack) {
// No need to send an ACK.
return frames;
}
// 3) 生成AckFrame后需要重置状态,以便后面的Ack从未确认的包开始
ResetAckStates();
....
// 4) 从uber_received_packet_manager_返回待确认的AckFrame信息
QuicFrame updated_ack_frame = GetUpdatedAckFrame();
...
frames.push_back(updated_ack_frame);
return frames;
}
- 从这个函数名的命名来看就是也许有机会发送
AckFrame
,上面函数分成4个步骤,首先通过GetAckTimeout()
函数判断AckFrame
的发送时间是否已经被赋值。如果已经被赋值这里返回true。 - 其次调用
ResetAckStates()
进行状态复位,其实现如下:
void QuicConnection::ResetAckStates() {
ack_alarm_->Cancel();
uber_received_packet_manager_.ResetAckStates(encryption_level_);
}
- 这个函数首先是将
Ack
的定时器进行取消,为什么要取消呢?因为发送Ack的时机有很多场景,其中定时器也属于其中一种,那么这里已经发送了,那么就将定时器进行重置。 - 其次调用
UberReceivedPacketManager::ResetAckStates()
进行处理,注意这里是握手阶段,其实现如下:
void UberReceivedPacketManager::ResetAckStates(
EncryptionLevel encryption_level) {
if (!supports_multiple_packet_number_spaces_) {
received_packet_managers_[0].ResetAckStates();
return;
}
received_packet_managers_[QuicUtils::GetPacketNumberSpace(encryption_level)]
.ResetAckStates();
if (encryption_level == ENCRYPTION_INITIAL) {
// After one Initial ACK is sent, the others should be sent 'immediately'.
received_packet_managers_[INITIAL_DATA].set_local_max_ack_delay(
kAlarmGranularity);
}
}
void QuicReceivedPacketManager::ResetAckStates() {
ack_frame_updated_ = false;//在第二节记录接收包的时候被赋成true
ack_timeout_ = QuicTime::Zero();//设置成0
num_retransmittable_packets_received_since_last_ack_sent_ = 0;//接收包数量设置成0
last_sent_largest_acked_ = LargestAcked(ack_frame_);//记录当前ack最大的PkgNumber
}
- 上述处理逻辑首先是调用
QuicReceivedPacketManager::ResetAckStates()
进行复位处理。注意这个复位并未将QuicReceivedPacketManager
模块中的ack_frame_
清空**。 - 除此之外值得注意的是对于
INITIAL_DATA
级别的level
这里将local_max_ack_delay_
设置成1Ms
了,Initial
包的AckFrame
为立即发送。 - 最后步骤4)通过调用
GetUpdatedAckFrame()
拿到AckFrame
,并进行聚合发送。
4.2)场景2:发送StreamFrame的时候发送AckFrame
- 发送
StreamFrame
和发送握手数据流程类似,导致流程如下:
005.png - 该场景和场景1基本一致,代码实现上也一致。
4.3)场景4:QuicConnection中的ack_alarm定时发送
-
在
006.pngQuicConnection
模块中定义了一个ack_alarm_
的定时器,该定时器负责定时发送AckFrame
,当然定时器的中断时间是动态刷新的,该定时器发送AckFrame
的实现逻辑如下:
-
其中定时器中断的核心代码如下:
void QuicConnection::SendAllPendingAcks() {
//1) 先取消定时器
ack_alarm_->Cancel();
//2) 获取INITIAL_DATA、HANDSHAKE_DATA、APPLICATION_DATA三个space中最小的ACK超时时间
QuicTime earliest_ack_timeout =
uber_received_packet_manager_.GetEarliestAckTimeout();
...
// 这里表示没收到包(在上次ACK后到现在)
if (!earliest_ack_timeout.IsInitialized()) {
return;
}
//3) 获取离当前最快超时的那个Level对应的时间,不同Leve超时时间可能不一样
for (int8_t i = INITIAL_DATA; i <= APPLICATION_DATA; ++i) {
const QuicTime ack_timeout = uber_received_packet_manager_.GetAckTimeout(
static_cast<PacketNumberSpace>(i));
if (!ack_timeout.IsInitialized()) {
continue;
}
if (!framer_.HasAnEncrypterForSpace(static_cast<PacketNumberSpace>(i))) {
// The key has been dropped.
continue;
}
if (ack_timeout > clock_->ApproximateNow() &&
ack_timeout > earliest_ack_timeout) {
// Always send the earliest ACK to make forward progress in case alarm
// fires early.
continue;
}
.....
ScopedEncryptionLevelContext context(
this, QuicUtils::GetEncryptionLevelToSendAckofSpace(
static_cast<PacketNumberSpace>(i)));
QuicFrames frames;
frames.push_back(uber_received_packet_manager_.GetUpdatedAckFrame(
static_cast<PacketNumberSpace>(i), clock_->ApproximateNow()));
const bool flushed = packet_creator_.FlushAckFrame(frames);
if (!flushed) {
// Connection is write blocked.
break;
}
// 最小Level的那个进行状态复位
ResetAckStates();
}
//4) 上面处理的事最先超时的那个level,假设还有其他level的超时时间有设置,这里根据第三节算出来的ACK时间进行定时器重设
const QuicTime timeout =
uber_received_packet_manager_.GetEarliestAckTimeout();
if (timeout.IsInitialized()) {
// If there are ACKs pending, re-arm ack alarm.
ack_alarm_->Update(timeout, kAlarmGranularity);
}
//5) 非应用数据直接返回
// Only try to bundle retransmittable data with ACK frame if default
// encryption level is forward secure.
if (encryption_level_ != ENCRYPTION_FORWARD_SECURE ||
!ShouldBundleRetransmittableFrameWithAck()) {
return;
}
consecutive_num_packets_with_no_retransmittable_frames_ = 0;
// 如果有重传帧要处理则立即返回
if (packet_creator_.HasPendingRetransmittableFrames() ||
visitor_->WillingAndAbleToWrite()) {
// There are pending retransmittable frames.
return;
}
// 6) 这里好像会发送WINDOW_UPDATE_FRAME
visitor_->OnAckNeedsRetransmittableFrame();
}
- 上述代码的逻辑处理有点复杂,这主要归结于
QUIC
支持IETF
聚合的问题,比如一个握手包同时还携带有应用数据。 - 上述代码首先是通过
INITIAL_DATA、HANDSHAKE_DATA、APPLICATION_DATA
进行遍历,找对最小需要ACK
的那个level
,进行ACK
处理,然后再对其他需要ACK
处理的level
进行定时器中断重设处理。 - 最后如果当前已经是握手完成状态的情况下进行6)操作,发送
WINDOW_UPDATE_FRAME
,本文不做处理,逻辑有点复杂后续再进行分析。
五、ACK定时器中断刷新机制介绍
-
在上一节中有提到
ack_alarm_
的更新是当处理完一个AckFrame
后,如果INITIAL_DATA、HANDSHAKE_DATA、APPLICATION_DATA
中还有需要进行Ack
的包,那么会对ack_alarm_
根据ack
包的超时时间进行更新。 -
除此之外在
007.pnggoogle quiche
代码中还有如下地方进行了定时器重设操作。
-
在
QuicConnection
模块中,ProcessUdpPacket(..)、SendXX()
等操作都会首先定义一个ScopedPacketFlusher flusher(this)
,这样在函数执行完后这个ScopedPacketFlusher
类会被析构,从而进入析构函数,而在析构函数中会对ack_alarm_
定时器进行更新。 -
有必要分析一下其析构函数
QuicConnection::ScopedPacketFlusher::~ScopedPacketFlusher() {
if (connection_ == nullptr || !connection_->connected()) {
return;
}
// 该成员在构造函数中会被设置成true
if (flush_and_set_pending_retransmission_alarm_on_delete_) {
// 1) 获取最先需要Ack的超时时间
const QuicTime ack_timeout =
connection_->uber_received_packet_manager_.GetEarliestAckTimeout();
// 如果有被赋值,说名有未确认的包收到
if (ack_timeout.IsInitialized()) {
// 1.1) 如果已经超时(说明需要发ack)了,但当前链接不可写,则取消定时器
if (ack_timeout <= connection_->clock_->ApproximateNow() &&
!connection_->CanWrite(NO_RETRANSMITTABLE_DATA)) {
// Cancel ACK alarm if connection is write blocked, and ACK will be
// sent when connection gets unblocked.
connection_->ack_alarm_->Cancel();
} else if (!connection_->ack_alarm_->IsSet() ||
connection_->ack_alarm_->deadline() > ack_timeout) {
// 1.2) 如果定时器未设置或者定时器的超时时间要比ACK包需要发送的时间要大,则需要重新更新定时器的超时时间
connection_->ack_alarm_->Update(ack_timeout, QuicTime::Delta::Zero());
}
}
//2 ) 若定时器已经超时
if (connection_->ack_alarm_->IsSet() &&
connection_->ack_alarm_->deadline() <=
connection_->clock_->ApproximateNow()) {
// An ACK needs to be sent right now. This ACK did not get bundled
// because either there was no data to write or packets were marked as
// received after frames were queued in the generator.
// 2.1 ) 若发送定时器也超时了则取消ack定时器
if (connection_->send_alarm_->IsSet() &&
connection_->send_alarm_->deadline() <=
connection_->clock_->ApproximateNow()) {
// If send alarm will go off soon, let send alarm send the ACK.
connection_->ack_alarm_->Cancel();
} else if (connection_->SupportsMultiplePacketNumberSpaces()) {
//2.2) 发送AckFrame
connection_->SendAllPendingAcks();
} else {
connection_->SendAck();
}
}
....
}
}
- 本节介绍的是
Ack
定时器中断更新机制,google quiche
项目的QuicConnection
模块在进行数据发送操作前都会定义一个ScopedPacketFlusher flusher(this)
,当函数栈调用完成后会进行析构,在其析构函数中对定时器进行了刷新操作,看上去是每当发送数据后都会进行定时器检测是否还有未被确认的包,如果有则重新设置定时器,确保在恰当的时机发送AckFrame
。 - 其次当收到对端的数据后
QuicConnection
模块会使用ProcessUdpPacket()
函数对报文进行解析,此处也会定义ScopedPacketFlusher flusher(this)
,所以当收到包后解析完成之后也会进行定时器超时设置,这些设置都是基于uber_received_packet_manager_
模块计算出来的Ack
超时时间进行设置的。 - 到此为止作为数据接收端对
AckFrame
的发送前处理就已经介绍完毕,接下来开始介绍发送端收到AckFrame
后对其处理操作。
六、发送端处理AckFrame
-
经过对代码梳理,
008.pngAckFrame
接收端处理逻辑大概如下:
-
本节将按照图(8)的流程对
AckFrame
处理所涉及到到的核心函数OnAckFrameStart()、OnAckRange()、OnAckTimestamp()、OnAckFrameEnd()
进行逐一分析。 -
在分析这些函数之前,首先需要学习一下在发送端发送包的时候记录了发送包的哪些信息,以及和哪些数据结构有关系,这样便于后面的分析。
009.png -
unacked_packets_
记录发送包消息,包括发送时间、发送字节数、以及包序号,当收到Ack
后需要从中获取对应的包号。 -
last_ack_frame
表示最近收到的AckFrame
。 -
packets_acked_
当AckFrame
收到后用于记录已经确认的包信息。 -
这里有必要读一下
AddSentPacket()
的实现:
6.0)AddSentPacket分析
void QuicUnackedPacketMap::AddSentPacket(SerializedPacket* mutable_packet,
TransmissionType transmission_type,
QuicTime sent_time, bool set_in_flight,
bool measure_rtt,
QuicEcnCodepoint ecn_codepoint) {
const SerializedPacket& packet = *mutable_packet;
QuicPacketNumber packet_number = packet.packet_number;
QuicPacketLength bytes_sent = packet.encrypted_length;
// 1) least_unacked_初始值为1,这里如果成立,则说明中间有包漏发了
while (least_unacked_ + unacked_packets_.size() < packet_number) {
unacked_packets_.push_back(QuicTransmissionInfo());
unacked_packets_.back().state = NEVER_SENT;
}
// 2) 构造QuicTransmissionInfo该结构为发送包的基础源数据结构
const bool has_crypto_handshake = packet.has_crypto_handshake == IS_HANDSHAKE;
QuicTransmissionInfo info(packet.encryption_level, transmission_type,
sent_time, bytes_sent, has_crypto_handshake,
packet.has_ack_frequency, ecn_codepoint);
// 这个packet.largest_acked在哪更新?
info.largest_acked = packet.largest_acked;
largest_sent_largest_acked_.UpdateMax(packet.largest_acked);
.....
// 更新截止当前最大的发送包号
largest_sent_packet_ = packet_number;
//3) 基础信息初始化,增加bytes_in_flight_、largest_sent_retransmittable_packets_
// 以及将info.in_flight设置成true,这些数据在后面收到ACK之后都会用到
if (set_in_flight) {
const PacketNumberSpace packet_number_space =
GetPacketNumberSpace(info.encryption_level);
bytes_in_flight_ += bytes_sent;
bytes_in_flight_per_packet_number_space_[packet_number_space] += bytes_sent;
++packets_in_flight_;
info.in_flight = true;
largest_sent_retransmittable_packets_[packet_number_space] = packet_number;
last_inflight_packet_sent_time_ = sent_time;
last_inflight_packets_sent_time_[packet_number_space] = sent_time;
}
//4 ) 将QuicTransmissionInfo结构插入到环形队列
unacked_packets_.push_back(std::move(info));
// Swap the retransmittable frames to avoid allocations.
// TODO(ianswett): Could use emplace_back when Chromium can.
if (has_crypto_handshake) {
last_crypto_packet_sent_time_ = sent_time;
}
//5) 这里将当retransmittable_frames进行保存,用于后续重传
mutable_packet->retransmittable_frames.swap(
unacked_packets_.back().retransmittable_frames);
}
-
简单总结一下就是为每个发送的包分配一个
QuicTransmissionInfo
,然后插入内部容器。 -
注意这里似乎在
QuicTransmissionInfo
结构中缓存了retransmittable_frames
信息,这个就是要用来重传的。 -
这说明重传
Frame
是保存在unacked_packets_
容器当中了。 -
这个数据结构有点复杂,大致数据成员如下图:
010.png -
QuicSentPacketManager
模块持有成员QuicUnackedPacketMap unacked_packets_
成员,而QuicUnackedPacketMap
数据结构中持有一个QuicheCircularDeque<QuicTransmissionInfo> unacked_packets_
的环形队列结构。 -
所以在
QuicSentPacketManager
模块中对其成员unacked_packets_
操作,实际上就是对QuicheCircularDeque<QuicTransmissionInfo> unacked_packets_
操作。
6.1) OnAckFrameStart()处理
bool QuicConnection::OnAckFrameStart(QuicPacketNumber largest_acked,
QuicTime::Delta ack_delay_time) {
....
//1) Received an old ack frame: ignoring
if (GetLargestReceivedPacketWithAck().IsInitialized() &&
last_received_packet_info_.header.packet_number <=
GetLargestReceivedPacketWithAck()) {
return true;
}
//2) 假设Ack的包序号比当前发送的包序号还大直接出错关闭连接
if (!sent_packet_manager_.GetLargestSentPacket().IsInitialized() ||
largest_acked > sent_packet_manager_.GetLargestSentPacket()) {
// We got an ack for data we have not sent.
CloseConnection(QUIC_INVALID_ACK_DATA, "Largest observed too high.",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return false;
}
//3) 使用QuicSentPacketManager模块进行处理
processing_ack_frame_ = true;
sent_packet_manager_.OnAckFrameStart(
largest_acked, ack_delay_time,
idle_network_detector_.time_of_last_received_packet());
return true;
}
- 这里引出一个重要模块
QuicSentPacketManager
发送管理,该模块记录发包信息,诸如丢包计算、RTT计算、拥塞控制等都和它有关系。
/**
* largest_acked:为本次AckFrame中最大的pkgNumber,也就是被确认的最大包号
* ack_delay_time:表示对端收到包后,到发送AckFrame的延迟
* ack_receive_time:表示local端收到该AckFrame的本地时间
*/
void QuicSentPacketManager::OnAckFrameStart(QuicPacketNumber largest_acked,
QuicTime::Delta ack_delay_time,
QuicTime ack_receive_time) {
....
//1) 尝试通过AckFrame的接收时间和延迟信息来更新RTT
rtt_updated_ =
MaybeUpdateRTT(largest_acked, ack_delay_time, ack_receive_time);
//2) 更新当前Ack包的延迟
last_ack_frame_.ack_delay_time = ack_delay_time;
// 得到ack_range[0]的迭代器
acked_packets_iter_ = last_ack_frame_.packets.rbegin();
}
- 第一步
OnAckFrameStart
最重要的事就是尝试更新rtt
,其实现如下:
bool QuicSentPacketManager::MaybeUpdateRTT(QuicPacketNumber largest_acked,
QuicTime::Delta ack_delay_time,
QuicTime ack_receive_time) {
// We rely on ack_delay_time to compute an RTT estimate, so we
// only update rtt when the largest observed gets acked and the acked packet
// is not useless.
//1) 这里表示largest_acked这个pkgNumber不在unacked_packets_容器管理的发包范围内
// 比如unacked_packets_记录了[10~100],但此时largest_acked为9或者101,则认为是无效的
if (!unacked_packets_.IsUnacked(largest_acked)) {
return false;
}
// We calculate the RTT based on the highest ACKed packet number, the lower
// packet numbers will include the ACK aggregation delay.
// 2) 根据包号得到发送包的传输信息,发送时间、发送字节数等
const QuicTransmissionInfo& transmission_info =
unacked_packets_.GetTransmissionInfo(largest_acked);
....
// 3) 以Ack的接收时间和该包的发送时间算出一个RTT时间,然后再结合ack_delay_time作为入参
// 通过RttStats模块对min_rtt和smooth_rtt进行计算和更新
QuicTime::Delta send_delta = ack_receive_time - transmission_info.sent_time;
const bool min_rtt_available = !rtt_stats_.min_rtt().IsZero();
rtt_stats_.UpdateRtt(send_delta, ack_delay_time, ack_receive_time);
....
return true;
}
-
QuicSentPacketManager
模块使用QuicUnackedPacketMap unacked_packets_
容器来缓存发包信息,当收到AckFrame
后和该容器中的发送的包进行校验等操作。 - 通过上述分析不难看出
OnAckFrameStart()
函数的核心作用就是计算min_rtt
和smooth_rtt
,本文重点是分析AckFrame
原理,所以这里对RTT
的计算和平滑处理不做分析。
6.2) OnAckRange()循环处理
- 为什么会是循环处理
AckRange
,回顾2.1节有提到AckFrame
的结构定义,当出现丢包的时候一个AckFrame
会出现多个range
,所以这里是一个一个进行处理。
bool QuicConnection::OnAckRange(QuicPacketNumber start, QuicPacketNumber end) {
....
sent_packet_manager_.OnAckRange(start, end);
return true;
}
void QuicSentPacketManager::OnAckRange(QuicPacketNumber start,
QuicPacketNumber end) {
// 1) 首次接收到AckFrame或者有新的Ack range并且最大应答包号并当前已经确认的最大包号要大
// 则更新当前AckFrame的最大应答包序号,同时更新unacked_packets_容器中最多以确认的包序号
// 注意这里的end是一个开区间,按照第二节的分析,这个range是[start end),前闭后开
if (!last_ack_frame_.largest_acked.IsInitialized() ||
end > last_ack_frame_.largest_acked + 1) {
// Largest acked increases.这里会更新unacked_packets_容器中的largest_acked_成员
unacked_packets_.IncreaseLargestAcked(end - 1);
last_ack_frame_.largest_acked = end - 1;
}
// 2) 如果收到的range 最大确认的包号比发送端最小未确认的包号要小则返回,比如最小未确认的包号为5,但是这个range为[1 5)
// Drop ack ranges which ack packets below least_unacked.
QuicPacketNumber least_unacked = unacked_packets_.GetLeastUnacked();
if (least_unacked.IsInitialized() && end <= least_unacked) {
return;
}
//3 ) 这里其实就是将[start end)之间已经确认的包的信息构造一个AckedPacket结构然后插入到packets_acked_容器尾部
start = std::max(start, least_unacked);
do {
QuicPacketNumber newly_acked_start = start;
// 在6.1中会设置成acked_packets_iter_ = last_ack_frame_.packets.rbegin()
if (acked_packets_iter_ != last_ack_frame_.packets.rend()) {
// 靠右遍历
newly_acked_start = std::max(start, acked_packets_iter_->max());
}
for (QuicPacketNumber acked = end - 1; acked >= newly_acked_start;
--acked) {
// Check if end is above the current range. If so add newly acked packets
// in descending order.
packets_acked_.push_back(AckedPacket(acked, 0, QuicTime::Zero()));
if (acked == FirstSendingPacketNumber()) {
break;
}
}
if (acked_packets_iter_ == last_ack_frame_.packets.rend() ||
start > acked_packets_iter_->min()) {
// Finish adding all newly acked packets.
return;
}
end = std::min(end, acked_packets_iter_->min());
++acked_packets_iter_;
} while (start < end);
}
- 经过
OnAckRange
的处理,会根据已经ack
包信息构造一个AckedPacket
结构然后插入到packets_acked_
容器当中。 - 到此为止,已经被确认的包的信息就被记录到
packets_acked_
当中了,同时unacked_packets_
也记录了当前已经被确认的最大包号。 -
last_ack_frame_
也记录了当前已经确认的最大包号。
6.3) OnAckFrameEnd()处理
bool QuicConnection::OnAckFrameEnd(
QuicPacketNumber start, const absl::optional<QuicEcnCounts>& ecn_counts) {
....
const bool one_rtt_packet_was_acked =
sent_packet_manager_.one_rtt_packet_acked();
const bool zero_rtt_packet_was_acked =
sent_packet_manager_.zero_rtt_packet_acked();
//1) 处理OnAckFrameEnd
const AckResult ack_result = sent_packet_manager_.OnAckFrameEnd(
idle_network_detector_.time_of_last_received_packet(),
last_received_packet_info_.header.packet_number,
last_received_packet_info_.decrypted_level, ecn_counts);
if (ack_result != PACKETS_NEWLY_ACKED &&
ack_result != NO_PACKETS_NEWLY_ACKED) {
// Error occurred (e.g., this ACK tries to ack packets in wrong packet
// number space), and this would cause the connection to be closed.
return false;
}
if (SupportsMultiplePacketNumberSpaces() && !one_rtt_packet_was_acked &&
sent_packet_manager_.one_rtt_packet_acked()) {
visitor_->OnOneRttPacketAcknowledged();
}
....
// Cancel the send alarm because new packets likely have been acked, which
// may change the congestion window and/or pacing rate. Canceling the alarm
// causes CanWrite to recalculate the next send time.
// 2) 取消发送定时器,按照注释说,在1)中可以计算一次拥塞控制,可以更改发送速率
if (send_alarm_->IsSet()) {
send_alarm_->Cancel();
}
if (supports_release_time_) {
// Update pace time into future because smoothed RTT is likely updated.
UpdateReleaseTimeIntoFuture();
}
SetLargestReceivedPacketWithAck(
last_received_packet_info_.header.packet_number);
//3) 后处理这里面可以更新重传定时器
PostProcessAfterAckFrame(ack_result == PACKETS_NEWLY_ACKED);
processing_ack_frame_ = false;
return connected_;
}
-
QuicConnection
中的OnAckFrameEnd
函数通过调用QuicSentPacketManager::OnAckFrameEnd
进行处理,
AckResult QuicSentPacketManager::OnAckFrameEnd(
QuicTime ack_receive_time, QuicPacketNumber ack_packet_number,/*这里是ack包的包号*/
EncryptionLevel ack_decrypted_level,
const absl::optional<QuicEcnCounts>& ecn_counts) {
// AddSentPacket中添加(这里得到在该次Ack之前的in flight的数据)
QuicByteCount prior_bytes_in_flight = unacked_packets_.bytes_in_flight();
QuicPacketCount newly_acked_ect0 = 0;
QuicPacketCount newly_acked_ect1 = 0;
PacketNumberSpace acked_packet_number_space =
QuicUtils::GetPacketNumberSpace(ack_decrypted_level);
// 这个地方在前面两步还未赋值,所以这里获取到的应该是上一次ack的最大包序号
QuicPacketNumber old_largest_acked =
unacked_packets_.GetLargestAckedOfPacketNumberSpace(
acked_packet_number_space);
// Reverse packets_acked_ so that it is in ascending order.
// 对acked中的元素进行逆序排,为什么这里要逆序排?因为在6.2的处理中,ack_range的内存布局是从大到小的,所以这里要逆序变成从小到大
std::reverse(packets_acked_.begin(), packets_acked_.end());
for (AckedPacket& acked_packet : packets_acked_) {
QuicTransmissionInfo* info =
unacked_packets_.GetMutableTransmissionInfo(acked_packet.packet_number);
// 这里应该还是为outgoning => state != NEVER_SENT && state != ACKED && state != UNACKABLE;
// 这里过滤不可ack的包信息
if (!QuicUtils::IsAckable(info->state)) {
...
continue;
}
.....
// 最后一步操作last_ack_frame_ 这是个成员变量,将已经ack得包序号添加到last_ack_frame_中的packets队列当中
last_ack_frame_.packets.Add(acked_packet.packet_number);
if (info->encryption_level == ENCRYPTION_HANDSHAKE) {
handshake_packet_acked_ = true;
} else if (info->encryption_level == ENCRYPTION_ZERO_RTT) {
zero_rtt_packet_acked_ = true;
} else if (info->encryption_level == ENCRYPTION_FORWARD_SECURE) {
one_rtt_packet_acked_ = true;
}
// 这个变量记录的是当前已被确认的最大包号(info->largest_acked在6.2中被更新)
largest_packet_peer_knows_is_acked_.UpdateMax(info->largest_acked);
if (supports_multiple_packet_number_spaces()) {
largest_packets_peer_knows_is_acked_[packet_number_space].UpdateMax(
info->largest_acked);
}
// If data is associated with the most recent transmission of this
// packet, then inform the caller.
if (info->in_flight) {//初始值为false,发送后应该会被设置成true
acked_packet.bytes_acked = info->bytes_sent;
} else {
// Unackable packets are skipped earlier.
largest_newly_acked_ = acked_packet.packet_number;
}
// ecn 处理
switch (info->ecn_codepoint) {
case ECN_NOT_ECT:
break;
case ECN_CE:
// ECN_CE should only happen in tests. Feedback validation doesn't track
// newly acked CEs, and if newly_acked_ect0 and newly_acked_ect1 are
// lower than expected that won't fail validation. So when it's CE don't
// increment anything.
break;
case ECN_ECT0:
++newly_acked_ect0;
if (info->in_flight) {
network_change_visitor_->OnInFlightEcnPacketAcked();
}
break;
case ECN_ECT1:
++newly_acked_ect1;
if (info->in_flight) {
network_change_visitor_->OnInFlightEcnPacketAcked();
}
break;
}
// 这里更新的是成员QuicUnackedPacketMap中的largest_acked_packets_成员
unacked_packets_.MaybeUpdateLargestAckedOfPacketNumberSpace(
packet_number_space, acked_packet.packet_number);
// 标记该包已经处理,这里暂步分析,后面分析重传原理的时候再行分析
MarkPacketHandled(acked_packet.packet_number, info, ack_receive_time,
last_ack_frame_.ack_delay_time,
acked_packet.receive_timestamp);
}
// Validate ECN feedback.
absl::optional<QuicEcnCounts> valid_ecn_counts;
if (GetQuicReloadableFlag(quic_send_ect1)) {
if (IsEcnFeedbackValid(acked_packet_number_space, ecn_counts,
newly_acked_ect0, newly_acked_ect1)) {
valid_ecn_counts = ecn_counts;
} else if (!old_largest_acked.IsInitialized() ||
old_largest_acked <
unacked_packets_.GetLargestAckedOfPacketNumberSpace(
acked_packet_number_space)) {
// RFC 9000 S13.4.2.1: "An endpoint MUST NOT fail ECN validation as a
// result of processing an ACK frame that does not increase the largest
// acknowledged packet number."
network_change_visitor_->OnInvalidEcnFeedback();
}
}
const bool acked_new_packet = !packets_acked_.empty();
PostProcessNewlyAckedPackets(ack_packet_number, ack_decrypted_level,
last_ack_frame_, ack_receive_time, rtt_updated_,
prior_bytes_in_flight, valid_ecn_counts);
if (valid_ecn_counts.has_value()) {
peer_ack_ecn_counts_[acked_packet_number_space] = valid_ecn_counts.value();
}
return acked_new_packet ? PACKETS_NEWLY_ACKED : NO_PACKETS_NEWLY_ACKED;
}
- 该函数首先是对
QuicSentPacketManager
中的packets_acked_
容器进行逆序排列,为什么需要排列在上述解释中有说明,这个容器记录着已被Ack
的包信息,其中每一个发送出去的包用QuicTransmissionInfo
结构来进行描述,被记录在unacked_packets_
容器当中,在AddSentPacket
中构造并插入。 - 然后遍历
packets_acked_
容器: - 1)根据每个
acked_packet.packet_number(已acked的包号)
来填充成员变量QuicAckFrame last_ack_frame_
中的成员packets
队列,这样这个QuicSentPacketManager
中的成员QuicAckFrame last_ack_frame_
就记录着所有已被Ack
的包了。` - 2) 根据每个
acked_packet.packet_number(已acked的包号)
从unacked_packets_
容器当中返回QuicTransmissionInfo
结构信息,利用该结构中存储的如bytes_sent
成员来为QuicSentPacketManager
模块中的largest_packet_peer_knows_is_acked_
成员赋值。 - 3)调用函数
MarkPacketHandled()
函数来更新QuicTransmissionInfo
信息,其中info->state
设置成ACKED
,info->in_flight
设置成false
,还有在6.0)
小节中提到的QuicTransmissionInfo
会保存重传Frame
信息,这里因为已经被Ack
了,也就是对端收到了,所以在这个函数中也会对其进行清理。 - 其次若有
ecn
信号包,则进行相关处理。 - 最后调用
PostProcessNewlyAckedPackets()
函数进行更复杂的逻辑处理如(丢包检测、重传等),该函数实现如下:
void QuicSentPacketManager::PostProcessNewlyAckedPackets(
QuicPacketNumber ack_packet_number, EncryptionLevel ack_decrypted_level,
const QuicAckFrame& ack_frame, QuicTime ack_receive_time, bool rtt_updated,
QuicByteCount prior_bytes_in_flight,
absl::optional<QuicEcnCounts> ecn_counts) {
...
// 1) 进行丢包检测,包括丢包率计算,以及重传操作处理
InvokeLossDetection(ack_receive_time);
// 2) 触发一次拥塞控制事件
MaybeInvokeCongestionEvent(
rtt_updated, prior_bytes_in_flight, ack_receive_time, ecn_counts,
peer_ack_ecn_counts_[QuicUtils::GetPacketNumberSpace(
ack_decrypted_level)]);
// 3) 这里会清除QuicUnackedPacketMap数据结构中已经无效的数据
// quiche::QuicheCircularDeque<QuicTransmissionInfo> unacked_packets_队列
// 同时会循环累加least_unacked_举个例子本次AckFrame携带的是[1 100),假设应答数据为[1 50),那么50以前的数据就会被擦除掉
// least_unacked_就会等于50
unacked_packets_.RemoveObsoletePackets();
// 4) 记录带宽信息?
sustained_bandwidth_recorder_.RecordEstimate(
send_algorithm_->InRecovery(), send_algorithm_->InSlowStart(),
send_algorithm_->BandwidthEstimate(), ack_receive_time, clock_->WallNow(),
rtt_stats_.smoothed_rtt());
....
// Remove packets below least unacked from all_packets_acked_ and
// last_ack_frame_.
// 5) GetLeastUnacked返回的是least_unacked_这里记录等于是把last_ack_frame_.packets
// 这个Set里面least_unacked_以前的记录清除掉
last_ack_frame_.packets.RemoveUpTo(unacked_packets_.GetLeastUnacked());
// 同时清楚时间戳
last_ack_frame_.received_packet_times.clear();
}
-
以上函数处理较为复杂,涉及到拥塞控制,本文不做分析,一共分成5个大步骤。大致的流程图如下:
011.png -
图(11)中涉及到丢包重传、和拥塞控制处理将在后文分析。
总结:
- 本文结合抓包、发送端和接收端代码学习,深入理解
google quiche
对AckFrame
的原理和实现,理解AckFrame
为后续的丢包重传、拥塞控制等模块学习做深入铺垫工作。 - 本文引出了丢包重传的概念,也引出了拥塞控制的概念
google quiche
的AckFrame
是控制的交通枢纽,为丢包率计算提供源数据,同时也为重传提供了源数据,并且拥塞控制也是依赖于AckFrame
。 -
google quiche
代码实现较为复杂,代码量比较多,学习google quiche
项目需要有耐心,其中3.2节中的AckFrame
发送时间的计算分成4种不同策略,这在实际项目中可能根据具体的业务需求进行配置和调整。 - 后文将深入分析
QuicFrame
重传的逻辑实现和原理。
网友评论