一聘惦、前言
通過上面五遍文章的學習碧囊,筆者對
google quiche
的代碼框架失暴,以及quic
的基本原理已經(jīng)有了較為清晰的認識峭沦,本文開始筆者將分析google quiche
項目中ACK
的實現(xiàn)原理枢贿,為后續(xù)分析google quiche
丟包重傳的實現(xiàn)已經(jīng)擁塞控制的實現(xiàn)做鋪墊寇仓。本文將從服務(wù)端和客戶端兩方面入手進行分析橱赠,著重分析
ACK
的發(fā)送時機厚棵、ACK FRAME
的定義和實現(xiàn)蕉世、AckFrame
的處理流程、以及相關(guān)模塊的核心邏輯實現(xiàn)等婆硬。
二狠轻、認識AckFrame
2.1)AckFrame協(xié)議認識
- 本節(jié)通過抓包問價并配合RFC9000先認識AckFrame中的內(nèi)容,首先我們我們先看一個抓包文件
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:一個可變長度的整數(shù),表示對端在生成
ACK Frame
幀之前接收到的最大數(shù)據(jù)包號彬犯。ACK Delay:以微秒為單位對確認延遲進行編碼的可變長度整數(shù); 參考 Section 13.2.5. 通過將字段中的值乘以2的
ack_delay_exponent
傳輸參數(shù)的冪來解碼; 參考 Section 18.2. 什么意思呢?AckFrame
發(fā)送端在發(fā)送該幀的時候,首先是會計算當前收到的最大包,經(jīng)歷多長時間開始發(fā)送AckFrame
,單位一般為微秒向楼,這里假設(shè)定義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:一個可變長度的整數(shù),這個字段代表的是對端在本次
Ack
的時候收到的所有包號,分成了多少段-1卢佣,為什么會出現(xiàn)分段重荠?假設(shè)丟包了就會出現(xiàn)分段了,按照圖(1)舉個例子,在這個抓包中虚茶,當前最大收到的包為43,其中丟了30和42號包,所以分成了3個段戈鲁。First ACK Range:一個可變長度的整數(shù),表示在最大已確認數(shù)之前被確認的連續(xù)報文數(shù)嘹叫。也就是說婆殿,該范圍內(nèi)確認的最小數(shù)據(jù)包由“最大確認”字段減去“第一個ACK范圍”的值確定。
ACK Ranges:包含未被確認(Gap)和已確認(ACK范圍)的數(shù)據(jù)包的附加范圍; 看 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
中維護了一個連續(xù)分范圍的Set
集合,當接收包的時候怕磨,若出現(xiàn)了丟包,那么這個Set
就會分段存儲消约,并且是使用左閉右([a,b)
)開的方法,其中a
和b
之間一定連續(xù)肠鲫,但是b
是沒收到的,按照圖(1)的抓包文件來看或粮,自上次Ack
后导饲,首先收到5號包,然后一直連續(xù)收到29號包氯材,再然后就收到了31號包渣锦,中間丟了30號包,接著一直連續(xù)收包到41號包氢哮。大致存儲成如下布局:[5,30) [31,42) [43,44) 左閉右
其中
[5,30)
代表的是ACK Range[2]
,[31,42)
對應(yīng)的是ACK Range[1]
,而[43,44)
對應(yīng)的是ACK Range[0]
也就是First ACK Range
,注意是逆序的袋毙。而每一個
ACK Range
的長度(ACK Range Length
)字段就是這個左閉右開區(qū)間中實際收到的包的個數(shù)減去1,也可以說成元素個數(shù)減1冗尤。再看
First ACK Range
字段其實就是對應(yīng)ACK Range[0]
的信息听盖,只不過是不包含gap
信息,因為這個gap
可以由前面的range
算出來,這里得到的就是第0
個range
真實接收到的包的個數(shù)減去1裂七。gap
指的是當前區(qū)間的開值和后面區(qū)間的閉值相減然后減去1媳溺,如上對于ACK Range[1](gap)
=43 - 42 - 1
,ACK Range[2](gap)
=31 - 30 - 1
,其實就可以理解成每個區(qū)間之間丟失包的個數(shù)。
2.2)QuicAckFrame數(shù)據(jù)結(jié)構(gòu)定義
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;
};
- 本節(jié)不做中文翻譯碍讯,英文注釋已經(jīng)很詳細
三悬蔽、接收端記錄接收包,并計算AckFrame的發(fā)送時間
- 接收端收到每一個數(shù)據(jù)包后會對包進行記錄捉兴,并且會嘗試更新超時時間蝎困,
QuicConnection
模塊在發(fā)送數(shù)據(jù)或者Ack
定時超時的時候會獲取該超時,如果已經(jīng)超時倍啥,則會在發(fā)送數(shù)據(jù)的時候聚合一個AckFrame
進行發(fā)送禾乘,而對于只接收數(shù)據(jù)不發(fā)送數(shù)據(jù)的接收端來說,AckFrame
的發(fā)送就依賴于Ack
定時器進行定時發(fā)送虽缕。 - 首先認識一下相關(guān)的數(shù)據(jù)結(jié)構(gòu)如下:
class QUIC_EXPORT_PRIVATE QuicConnection
: .... {
....
UberReceivedPacketManager uber_received_packet_manager_;
};
QuicConnection
模塊中有一個UberReceivedPacketManager
類型的成員變量始藕,該類用于收包管理,該類依賴QuicReceivedPacketManager
模塊完成對收包的管理,對外提供RecordPacketReceived()氮趋、GetUpdatedAckFrame()伍派、MaybeUpdateAckTimeout()、ResetAckStates()
等核心Api
來完成接收端發(fā)送AckFrame
的核心業(yè)務(wù)邏輯剩胁。-
其中當接收到一個包之后诉植,大致的處理流程如下:
002.png 根據(jù)圖(2)的流程不難看出,當收到一個包后首先是解析昵观、解析完后通過步驟#2對包進行記錄晾腔,記錄完后嘗試更新
Ack
超時時間舌稀,接下來本節(jié)著重分析這兩個核心函數(shù)的實現(xiàn):
3.1) RecordPacketReceived函數(shù)分析
-
UberReceivedPacketManager
模塊的RecordPacketReceived()
API用于完成對包的信息記錄,其具體實現(xiàn)如下:
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代表的是截止當前收到的最大包號,如果已經(jīng)收到的最大包號比當前收到的包號要大剔应,那就是亂序了
bool packet_reordered = false;
if (LargestAcked(ack_frame_).IsInitialized() &&
LargestAcked(ack_frame_) > packet_number) {
// Record how out of order stats.
// 更新統(tǒng)計信息
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來進行實現(xiàn)潮罪,其中每一個范圍都是可連續(xù)的收到的包如
// [0,100) [101,200) 表示0~99已經(jīng)收到,101~199已經(jīng)收到领斥,100號丟失,如果完全沒有丟失沃暗,那么就是[0 200]月洛,當中間出現(xiàn)亂序之后,假設(shè)又收到了已亂序的包
// ack_frame_.packets可以將兩個分段合并成一個分段孽锥,如上嚼黔,一開始丟了100號包,假設(shè)在收到101號包之后又收到了100號包惜辑,那么這個隊列的就變成[0,200)了
ack_frame_.packets.Add(packet_number);
//4) 嘗試丟棄最小段范圍的包唬涧,默認初始化的時候ack_frame_.packets隊列會設(shè)置一個最大范圍限制,默認是255,也就是最多可容納255個范圍連續(xù)的包盛撑,超過了則會將
// 最小的范圍進行丟棄
MaybeTrimAckRanges();
//5) 對接收時間戳進行記錄碎节,這里需要雙向協(xié)商,如果使用時間戳那么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
函數(shù)的核心作用是根據(jù)收到包的PacketNumber
介粘、時間戳殖氏、ECN等信息,將其保存到QuicReceivedPacketManager
模塊的成員變量ack_frame_
當中姻采。
3.2) MaybeUpdateAckTimeout函數(shù)計算AckFrame發(fā)送時間
- 按照圖(2)的流程接收端對收到的包的處理分成很多個流程雅采,當解析出實際的
QuicFrame
以及包處理完成后,會調(diào)用QuicConnection::MaybeUpdateAckTimeout()
函數(shù),而該函數(shù)的實現(xiàn)主要是bypass調(diào)用如下:
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_) {//由于是單線程慨亲,按照流程基本不可能發(fā)生
// ACK frame has not been updated, nothing to do.
return;
}
// 1) 亂序處理婚瓜,last_sent_largest_acked_表示,上一次已經(jīng)發(fā)送的QuicAckFrame中已應(yīng)答的最大包序號刑棵,這里假設(shè)上次Ack的時候,最大包序號是20闰渔,
// 然后19號丟了而此時又收到了19號包,那么Ack的超時時間設(shè)置成Now,也就是應(yīng)該立即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) 統(tǒng)計自上次發(fā)送ack自當前收到的包的數(shù)量+1
++num_retransmittable_packets_received_since_last_ack_sent_;
/* 3) 嘗試更新AckFrame的發(fā)送頻率
* 3.1) 如果當前收到了AckFrequencyFrame铐望,此處不更新
* 3.2) 如果當前收到的包號 < 當前未acked的包號least_received_packet_number_ + min_received_before_ack_decimation_(100)
* 也就是100個包以內(nèi)不更新AckFrame的頻率
* 3.3) 否則 unlimited_ack_decimation_ 默認為false冈涧,表示不限制收到多少個包后進行ack,
* 可通過協(xié)商QuicTag kAKDU = TAG('A', 'K', 'D', 'U')來配置該值為true
* ack_frequency_ = unlimited_ack_decimation_
* ? std::numeric_limits<size_t>::max()
* : kMaxRetransmittablePacketsBeforeAck;//默認值是10個包
* 默認10個包需要發(fā)送Ack茂附?
*/
MaybeUpdateAckFrequency(last_received_packet_number);
// 4) 基于#3的計算基礎(chǔ)會得到一個ack_frequency_,如果截止上一次發(fā)送AckFrame到當前收到了大于ack_frequency_(這里算出來是10個或者2^16 - 1)個包
// 則設(shè)置ack_timeout_為Now,表示可以立即發(fā)送AckFrame督弓。
if (num_retransmittable_packets_received_since_last_ack_sent_ >=
ack_frequency_) {
ack_timeout_ = now;
return;
}
/* 5) 基于丟包的AckTime決策
* 5.1) 當設(shè)置one_immediate_ack_為true营曼,也就是配置QuicTag k1ACK = TAG('1', 'A', 'C', 'K')的情況下,只要發(fā)生亂序或者丟包愚隧,則立即響應(yīng)發(fā)送Ack
* 5.2) 如5.1 未配置蒂阱,也就是默認情況如果出現(xiàn)丟包,并且出現(xiàn)丟包后收到的連續(xù)包的數(shù)量小于4以內(nèi)則立即進行Ack
*/
if (!ignore_order_ && HasNewMissingPackets()) {
ack_timeout_ = now;
return;
}
//6) 沒有出現(xiàn)丟包,則在當前收包時間的基礎(chǔ)上加上一個最大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
允許發(fā)送方向接收方傳輸一個幀狂塘,其中包含了關(guān)于ACK包
發(fā)送頻率的參數(shù)录煤。通過發(fā)送AckFrequencyFrame
,發(fā)送方可以調(diào)整ACK
包的發(fā)送頻率荞胡,以適應(yīng)當前網(wǎng)絡(luò)條件和性能需求妈踊。 - 通過上述代碼分析可以得出
MaybeUpdateAckTimeout
函數(shù)的核心作用是對AckFrame
的發(fā)送時機進行決策,一共分成四種決策泪漂。 - 決策1)亂序廊营,并且在上次發(fā)送完
AckFrame
后又收到了亂序包,則應(yīng)該立即發(fā)送AckFrame
萝勤。 - 決策2)基于已收包數(shù)量露筒,默認閾值為10個包,該策略可通過配置進行忽略敌卓,當每收到10個包的時候應(yīng)該進行
Ack
慎式。 - 決策3)基于丟包,看上去是只要出現(xiàn)了丟包就會立即發(fā)送
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_
表示當只要出現(xiàn)丟包的時候就進行Ack
,但是默認為false,而后面的邏輯看上去有點不大好理解,這里主要的原因是quic
支持聚合包瞬捕,而按照圖(2)的流程5是在QuicConnection::OnPacketComplete()
函數(shù)中回調(diào)的,這個地方假設(shè)出現(xiàn)了聚合包舵抹,那么就會存在一個UDP包中包含了好幾個QuicIetf
包肪虎,所以這里的ack_frame_.packets.LastIntervalLength()
有可能就會大于kMaxPacketsAfterNewMissing
。 - 意思就是假設(shè)一開始收到了10號包惧蛹,然后丟了11號包扇救,然后又連續(xù)收到了12~20號(聚合)包,這個時候就不需要立即發(fā)送
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個包以內(nèi)返回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個包以內(nèi),如果是則最大延遲為
25Ms
像云。如超過100個包锌雀,則為25Ms
和當前0.25*最小RTT
取最小值蚂夕。
四、接收端AckFrame發(fā)送時機介紹
- 在第三節(jié)中有分析了
AckFrame
的超時時間,也就滿足發(fā)送AckFrame
的條件腋逆,本節(jié)開始從代碼層面分析作為接收端婿牍,AckFrame
的發(fā)送時機有哪些。
4.1)場景1:發(fā)送握手數(shù)據(jù)的時候發(fā)送AckFrame
- 首先我們看握手階段惩歉,當服務(wù)端收到客戶端的
Initial
后等脂,服務(wù)端在回Intial
包的時候就會發(fā)送AckFrame
,同理客戶端收到服務(wù)端的Initial
+handshake
包后也會回復AckFrame
。
003.png -
其中圖(3)無論是客戶端還是服務(wù)端撑蚌,其代碼處理邏輯基本一致上遥,如下:
004.png - 該流程說明在發(fā)送握手包的時候是有機會發(fā)送
AckFrame
的,但是要注意圖(4)中的#1,如果第一步通過GetAckTimeout()
返回的超時時間已經(jīng)被初始化争涌,才會有步驟2和步驟3粉楚,否則是不會發(fā)送的。 - 很顯然對于握手階段服務(wù)端收到握手包后會立即處理第煮,從而會觸發(fā)第三節(jié)提到的
AckFrame
發(fā)送超時時間的計算,也就是此時的#1是一定會返回有值的抑党,透過代碼來看可能會更直觀包警。
const QuicFrames QuicConnection::MaybeBundleAckOpportunistically() {
/* 1) 如果支持發(fā)送AckFrameFrequency 幀(ack 頻率控制幀),并且下一個要發(fā)送的pkgNumber大于100+首次發(fā)送的pkgNumber
* 也就是每100個包發(fā)送一次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時間是否已經(jīng)被賦值,按照代碼實現(xiàn)來看底靠,對于及發(fā)送又接收的端來說害晦,
// 這里基本是在發(fā)送數(shù)據(jù)之前,如果有收到還未確認的包暑中,理論上每次發(fā)送數(shù)據(jù)的時候都會發(fā)送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后需要重置狀態(tài)壹瘟,以便后面的Ack從未確認的包開始
ResetAckStates();
....
// 4) 從uber_received_packet_manager_返回待確認的AckFrame信息
QuicFrame updated_ack_frame = GetUpdatedAckFrame();
...
frames.push_back(updated_ack_frame);
return frames;
}
- 從這個函數(shù)名的命名來看就是也許有機會發(fā)送
AckFrame
,上面函數(shù)分成4個步驟,首先通過GetAckTimeout()
函數(shù)判斷AckFrame
的發(fā)送時間是否已經(jīng)被賦值鳄逾。如果已經(jīng)被賦值這里返回true稻轨。 - 其次調(diào)用
ResetAckStates()
進行狀態(tài)復位,其實現(xiàn)如下:
void QuicConnection::ResetAckStates() {
ack_alarm_->Cancel();
uber_received_packet_manager_.ResetAckStates(encryption_level_);
}
- 這個函數(shù)首先是將
Ack
的定時器進行取消雕凹,為什么要取消呢殴俱?因為發(fā)送Ack的時機有很多場景,其中定時器也屬于其中一種枚抵,那么這里已經(jīng)發(fā)送了线欲,那么就將定時器進行重置。 - 其次調(diào)用
UberReceivedPacketManager::ResetAckStates()
進行處理汽摹,注意這里是握手階段李丰,其實現(xiàn)如下:
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;//在第二節(jié)記錄接收包的時候被賦成true
ack_timeout_ = QuicTime::Zero();//設(shè)置成0
num_retransmittable_packets_received_since_last_ack_sent_ = 0;//接收包數(shù)量設(shè)置成0
last_sent_largest_acked_ = LargestAcked(ack_frame_);//記錄當前ack最大的PkgNumber
}
- 上述處理邏輯首先是調(diào)用
QuicReceivedPacketManager::ResetAckStates()
進行復位處理。注意這個復位并未將QuicReceivedPacketManager
模塊中的ack_frame_
清空**逼泣。 - 除此之外值得注意的是對于
INITIAL_DATA
級別的level
這里將local_max_ack_delay_
設(shè)置成1Ms
了趴泌,Initial
包的AckFrame
為立即發(fā)送舟舒。 - 最后步驟4)通過調(diào)用
GetUpdatedAckFrame()
拿到AckFrame
,并進行聚合發(fā)送踱讨。
4.2)場景2:發(fā)送StreamFrame的時候發(fā)送AckFrame
- 發(fā)送
StreamFrame
和發(fā)送握手數(shù)據(jù)流程類似魏蔗,導致流程如下:
005.png - 該場景和場景1基本一致,代碼實現(xiàn)上也一致痹筛。
4.3)場景4:QuicConnection中的ack_alarm定時發(fā)送
-
在
QuicConnection
模塊中定義了一個ack_alarm_
的定時器莺治,該定時器負責定時發(fā)送AckFrame
,當然定時器的中斷時間是動態(tài)刷新的帚稠,該定時器發(fā)送AckFrame
的實現(xiàn)邏輯如下:
006.png 其中定時器中斷的核心代碼如下:
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后到現(xiàn)在)
if (!earliest_ack_timeout.IsInitialized()) {
return;
}
//3) 獲取離當前最快超時的那個Level對應(yīng)的時間滋早,不同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的那個進行狀態(tài)復位
ResetAckStates();
}
//4) 上面處理的事最先超時的那個level,假設(shè)還有其他level的超時時間有設(shè)置,這里根據(jù)第三節(jié)算出來的ACK時間進行定時器重設(shè)
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) 非應(yīng)用數(shù)據(jù)直接返回
// 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) 這里好像會發(fā)送WINDOW_UPDATE_FRAME
visitor_->OnAckNeedsRetransmittableFrame();
}
- 上述代碼的邏輯處理有點復雜榄审,這主要歸結(jié)于
QUIC
支持IETF
聚合的問題,比如一個握手包同時還攜帶有應(yīng)用數(shù)據(jù)杆麸。 - 上述代碼首先是通過
INITIAL_DATA搁进、HANDSHAKE_DATA、APPLICATION_DATA
進行遍歷昔头,找對最小需要ACK
的那個level
饼问,進行ACK
處理,然后再對其他需要ACK
處理的level
進行定時器中斷重設(shè)處理揭斧。 - 最后如果當前已經(jīng)是握手完成狀態(tài)的情況下進行6)操作,發(fā)送
WINDOW_UPDATE_FRAME
莱革,本文不做處理,邏輯有點復雜后續(xù)再進行分析讹开。
五盅视、ACK定時器中斷刷新機制介紹
在上一節(jié)中有提到
ack_alarm_
的更新是當處理完一個AckFrame
后,如果INITIAL_DATA旦万、HANDSHAKE_DATA闹击、APPLICATION_DATA
中還有需要進行Ack
的包,那么會對ack_alarm_
根據(jù)ack
包的超時時間進行更新成艘。-
除此之外在
google quiche
代碼中還有如下地方進行了定時器重設(shè)操作拇砰。
007.png 在
QuicConnection
模塊中,ProcessUdpPacket(..)狰腌、SendXX()
等操作都會首先定義一個ScopedPacketFlusher flusher(this)
除破,這樣在函數(shù)執(zhí)行完后這個ScopedPacketFlusher
類會被析構(gòu),從而進入析構(gòu)函數(shù),而在析構(gòu)函數(shù)中會對ack_alarm_
定時器進行更新琼腔。有必要分析一下其析構(gòu)函數(shù)
QuicConnection::ScopedPacketFlusher::~ScopedPacketFlusher() {
if (connection_ == nullptr || !connection_->connected()) {
return;
}
// 該成員在構(gòu)造函數(shù)中會被設(shè)置成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) 如果已經(jīng)超時(說明需要發(fā)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) 如果定時器未設(shè)置或者定時器的超時時間要比ACK包需要發(fā)送的時間要大,則需要重新更新定時器的超時時間
connection_->ack_alarm_->Update(ack_timeout, QuicTime::Delta::Zero());
}
}
//2 ) 若定時器已經(jīng)超時
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 ) 若發(fā)送定時器也超時了則取消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) 發(fā)送AckFrame
connection_->SendAllPendingAcks();
} else {
connection_->SendAck();
}
}
....
}
}
- 本節(jié)介紹的是
Ack
定時器中斷更新機制,google quiche
項目的QuicConnection
模塊在進行數(shù)據(jù)發(fā)送操作前都會定義一個ScopedPacketFlusher flusher(this)
,當函數(shù)棧調(diào)用完成后會進行析構(gòu)光坝,在其析構(gòu)函數(shù)中對定時器進行了刷新操作尸诽,看上去是每當發(fā)送數(shù)據(jù)后都會進行定時器檢測是否還有未被確認的包,如果有則重新設(shè)置定時器盯另,確保在恰當?shù)臅r機發(fā)送AckFrame
性含。 - 其次當收到對端的數(shù)據(jù)后
QuicConnection
模塊會使用ProcessUdpPacket()
函數(shù)對報文進行解析,此處也會定義ScopedPacketFlusher flusher(this)
,所以當收到包后解析完成之后也會進行定時器超時設(shè)置鸳惯,這些設(shè)置都是基于uber_received_packet_manager_
模塊計算出來的Ack
超時時間進行設(shè)置的商蕴。 - 到此為止作為數(shù)據(jù)接收端對
AckFrame
的發(fā)送前處理就已經(jīng)介紹完畢,接下來開始介紹發(fā)送端收到AckFrame
后對其處理操作芝发。
六绪商、發(fā)送端處理AckFrame
-
經(jīng)過對代碼梳理,
AckFrame
接收端處理邏輯大概如下:
008.png 本節(jié)將按照圖(8)的流程對
AckFrame
處理所涉及到到的核心函數(shù)OnAckFrameStart()辅鲸、OnAckRange()格郁、OnAckTimestamp()、OnAckFrameEnd()
進行逐一分析独悴。-
在分析這些函數(shù)之前例书,首先需要學習一下在發(fā)送端發(fā)送包的時候記錄了發(fā)送包的哪些信息,以及和哪些數(shù)據(jù)結(jié)構(gòu)有關(guān)系刻炒,這樣便于后面的分析决采。
009.png unacked_packets_
記錄發(fā)送包消息,包括發(fā)送時間落蝙、發(fā)送字節(jié)數(shù)织狐、以及包序號暂幼,當收到Ack
后需要從中獲取對應(yīng)的包號筏勒。last_ack_frame
表示最近收到的AckFrame
。packets_acked_
當AckFrame
收到后用于記錄已經(jīng)確認的包信息宿接。這里有必要讀一下
AddSentPacket()
的實現(xiàn):
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诚隙,這里如果成立囊蓝,則說明中間有包漏發(fā)了
while (least_unacked_ + unacked_packets_.size() < packet_number) {
unacked_packets_.push_back(QuicTransmissionInfo());
unacked_packets_.back().state = NEVER_SENT;
}
// 2) 構(gòu)造QuicTransmissionInfo該結(jié)構(gòu)為發(fā)送包的基礎(chǔ)源數(shù)據(jù)結(jié)構(gòu)
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);
.....
// 更新截止當前最大的發(fā)送包號
largest_sent_packet_ = packet_number;
//3) 基礎(chǔ)信息初始化,增加bytes_in_flight_捐顷、largest_sent_retransmittable_packets_
// 以及將info.in_flight設(shè)置成true,這些數(shù)據(jù)在后面收到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結(jié)構(gòu)插入到環(huán)形隊列
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進行保存雨效,用于后續(xù)重傳
mutable_packet->retransmittable_frames.swap(
unacked_packets_.back().retransmittable_frames);
}
簡單總結(jié)一下就是為每個發(fā)送的包分配一個
QuicTransmissionInfo
迅涮,然后插入內(nèi)部容器。注意這里似乎在
QuicTransmissionInfo
結(jié)構(gòu)中緩存了retransmittable_frames
信息徽龟,這個就是要用來重傳的叮姑。這說明重傳
Frame
是保存在unacked_packets_
容器當中了。-
這個數(shù)據(jù)結(jié)構(gòu)有點復雜,大致數(shù)據(jù)成員如下圖:
010.png QuicSentPacketManager
模塊持有成員QuicUnackedPacketMap unacked_packets_
成員传透,而QuicUnackedPacketMap
數(shù)據(jù)結(jié)構(gòu)中持有一個QuicheCircularDeque<QuicTransmissionInfo> unacked_packets_
的環(huán)形隊列結(jié)構(gòu)耘沼。所以在
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) 假設(shè)Ack的包序號比當前發(fā)送的包序號還大直接出錯關(guān)閉連接
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
發(fā)送管理群嗤,該模塊記錄發(fā)包信息,諸如丟包計算兵琳、RTT計算狂秘、擁塞控制等都和它有關(guān)系。
/**
* largest_acked:為本次AckFrame中最大的pkgNumber闰围,也就是被確認的最大包號
* ack_delay_time:表示對端收到包后赃绊,到發(fā)送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
,其實現(xiàn)如下:
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_容器管理的發(fā)包范圍內(nèi)
// 比如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) 根據(jù)包號得到發(fā)送包的傳輸信息,發(fā)送時間羡榴、發(fā)送字節(jié)數(shù)等
const QuicTransmissionInfo& transmission_info =
unacked_packets_.GetTransmissionInfo(largest_acked);
....
// 3) 以Ack的接收時間和該包的發(fā)送時間算出一個RTT時間碧查,然后再結(jié)合ack_delay_time作為入?yún)? // 通過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_
容器來緩存發(fā)包信息,當收到AckFrame
后和該容器中的發(fā)送的包進行校驗等操作校仑。 - 通過上述分析不難看出
OnAckFrameStart()
函數(shù)的核心作用就是計算min_rtt
和smooth_rtt
忠售,本文重點是分析AckFrame
原理,所以這里對RTT
的計算和平滑處理不做分析迄沫。
6.2) OnAckRange()循環(huán)處理
- 為什么會是循環(huán)處理
AckRange
稻扬,回顧2.1節(jié)有提到AckFrame
的結(jié)構(gòu)定義,當出現(xiàn)丟包的時候一個AckFrame
會出現(xiàn)多個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并且最大應(yīng)答包號并當前已經(jīng)確認的最大包號要大
// 則更新當前AckFrame的最大應(yīng)答包序號泰佳,同時更新unacked_packets_容器中最多以確認的包序號
// 注意這里的end是一個開區(qū)間,按照第二節(jié)的分析,這個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 最大確認的包號比發(fā)送端最小未確認的包號要小則返回逝她,比如最小未確認的包號為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)之間已經(jīng)確認的包的信息構(gòu)造一個AckedPacket結(jié)構(gòu)然后插入到packets_acked_容器尾部
start = std::max(start, least_unacked);
do {
QuicPacketNumber newly_acked_start = start;
// 在6.1中會設(shè)置成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);
}
- 經(jīng)過
OnAckRange
的處理睬捶,會根據(jù)已經(jīng)ack
包信息構(gòu)造一個AckedPacket
結(jié)構(gòu)然后插入到packets_acked_
容器當中黔宛。 - 到此為止,已經(jīng)被確認的包的信息就被記錄到
packets_acked_
當中了擒贸,同時unacked_packets_
也記錄了當前已經(jīng)被確認的最大包號臀晃。 -
last_ack_frame_
也記錄了當前已經(jīng)確認的最大包號。
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) 取消發(fā)送定時器介劫,按照注釋說徽惋,在1)中可以計算一次擁塞控制,可以更改發(fā)送速率
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
函數(shù)通過調(diào)用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的數(shù)據(jù))
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);
// 這個地方在前面兩步還未賦值险绘,所以這里獲取到的應(yīng)該是上一次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的內(nèi)存布局是從大到小的,所以這里要逆序變成從小到大
std::reverse(packets_acked_.begin(), packets_acked_.end());
for (AckedPacket& acked_packet : packets_acked_) {
QuicTransmissionInfo* info =
unacked_packets_.GetMutableTransmissionInfo(acked_packet.packet_number);
// 這里應(yīng)該還是為outgoning => state != NEVER_SENT && state != ACKED && state != UNACKABLE;
// 這里過濾不可ack的包信息
if (!QuicUtils::IsAckable(info->state)) {
...
continue;
}
.....
// 最后一步操作last_ack_frame_ 這是個成員變量,將已經(jīng)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,發(fā)送后應(yīng)該會被設(shè)置成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);
// 標記該包已經(jīng)處理,這里暫步分析隆圆,后面分析重傳原理的時候再行分析
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;
}
- 該函數(shù)首先是對
QuicSentPacketManager
中的packets_acked_
容器進行逆序排列,為什么需要排列在上述解釋中有說明,這個容器記錄著已被Ack
的包信息漱挚,其中每一個發(fā)送出去的包用QuicTransmissionInfo
結(jié)構(gòu)來進行描述,被記錄在unacked_packets_
容器當中渺氧,在AddSentPacket
中構(gòu)造并插入旨涝。 - 然后遍歷
packets_acked_
容器: - 1)根據(jù)每個
acked_packet.packet_number(已acked的包號)
來填充成員變量QuicAckFrame last_ack_frame_
中的成員packets
隊列,這樣這個QuicSentPacketManager
中的成員QuicAckFrame last_ack_frame_
就記錄著所有已被Ack
的包了。` - 2) 根據(jù)每個
acked_packet.packet_number(已acked的包號)
從unacked_packets_
容器當中返回QuicTransmissionInfo
結(jié)構(gòu)信息侣背,利用該結(jié)構(gòu)中存儲的如bytes_sent
成員來為QuicSentPacketManager
模塊中的largest_packet_peer_knows_is_acked_
成員賦值白华。 - 3)調(diào)用函數(shù)
MarkPacketHandled()
函數(shù)來更新QuicTransmissionInfo
信息,其中info->state
設(shè)置成ACKED
贩耐,info->in_flight
設(shè)置成false
弧腥,還有在6.0)
小節(jié)中提到的QuicTransmissionInfo
會保存重傳Frame
信息,這里因為已經(jīng)被Ack
了潮太,也就是對端收到了管搪,所以在這個函數(shù)中也會對其進行清理。 - 其次若有
ecn
信號包铡买,則進行相關(guān)處理更鲁。 - 最后調(diào)用
PostProcessNewlyAckedPackets()
函數(shù)進行更復雜的邏輯處理如(丟包檢測、重傳等)奇钞,該函數(shù)實現(xiàn)如下:
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) 觸發(fā)一次擁塞控制事件
MaybeInvokeCongestionEvent(
rtt_updated, prior_bytes_in_flight, ack_receive_time, ecn_counts,
peer_ack_ecn_counts_[QuicUtils::GetPacketNumberSpace(
ack_decrypted_level)]);
// 3) 這里會清除QuicUnackedPacketMap數(shù)據(jù)結(jié)構(gòu)中已經(jīng)無效的數(shù)據(jù)
// quiche::QuicheCircularDeque<QuicTransmissionInfo> unacked_packets_隊列
// 同時會循環(huán)累加least_unacked_舉個例子本次AckFrame攜帶的是[1 100),假設(shè)應(yīng)答數(shù)據(jù)為[1 50),那么50以前的數(shù)據(jù)就會被擦除掉
// 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();
}
-
以上函數(shù)處理較為復雜媒至,涉及到擁塞控制,本文不做分析谷徙,一共分成5個大步驟拒啰。大致的流程圖如下:
011.png 圖(11)中涉及到丟包重傳、和擁塞控制處理將在后文分析蒂胞。
總結(jié):
- 本文結(jié)合抓包图呢、發(fā)送端和接收端代碼學習条篷,深入理解
google quiche
對AckFrame
的原理和實現(xiàn)骗随,理解AckFrame
為后續(xù)的丟包重傳、擁塞控制等模塊學習做深入鋪墊工作赴叹。 - 本文引出了丟包重傳的概念鸿染,也引出了擁塞控制的概念
google quiche
的AckFrame
是控制的交通樞紐,為丟包率計算提供源數(shù)據(jù)乞巧,同時也為重傳提供了源數(shù)據(jù)涨椒,并且擁塞控制也是依賴于AckFrame
。 -
google quiche
代碼實現(xiàn)較為復雜,代碼量比較多蚕冬,學習google quiche
項目需要有耐心免猾,其中3.2節(jié)中的AckFrame
發(fā)送時間的計算分成4種不同策略,這在實際項目中可能根據(jù)具體的業(yè)務(wù)需求進行配置和調(diào)整囤热。 - 后文將深入分析
QuicFrame
重傳的邏輯實現(xiàn)和原理猎提。