google quic ack工作原理

一聘惦、前言

二狠轻、認識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 (..)],
}

Figure 25: ACK Frame Format

  • 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),
}

Figure 26: ACK Ranges

  • 要怎么認識這個ACK Range婆芦,在google quiche中維護了一個連續(xù)分范圍的Set集合,當接收包的時候怕磨,若出現(xiàn)了丟包,那么這個Set就會分段存儲消约,并且是使用左閉右([a,b))開的方法,其中ab之間一定連續(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算出來,這里得到的就是第0range真實接收到的包的個數(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_rttsmooth_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 quicheAckFrame的原理和實現(xiàn)骗随,理解AckFrame為后續(xù)的丟包重傳、擁塞控制等模塊學習做深入鋪墊工作赴叹。
  • 本文引出了丟包重傳的概念鸿染,也引出了擁塞控制的概念google quicheAckFrame是控制的交通樞紐,為丟包率計算提供源數(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)和原理猎提。

參考文獻

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市旁蔼,隨后出現(xiàn)的幾起案子锨苏,更是在濱河造成了極大的恐慌,老刑警劉巖棺聊,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伞租,死亡現(xiàn)場離奇詭異,居然都是意外死亡限佩,警方通過查閱死者的電腦和手機葵诈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祟同,“玉大人驯击,你說我怎么就攤上這事∧涂鳎” “怎么了徊都?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長广辰。 經(jīng)常有香客問我暇矫,道長,這世上最難降的妖魔是什么择吊? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任李根,我火速辦了婚禮,結(jié)果婚禮上几睛,老公的妹妹穿的比我還像新娘房轿。我一直安慰自己,他們只是感情好所森,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布囱持。 她就那樣靜靜地躺著,像睡著了一般焕济。 火紅的嫁衣襯著肌膚如雪纷妆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天晴弃,我揣著相機與錄音掩幢,去河邊找鬼逊拍。 笑死,一個胖子當著我的面吹牛际邻,可吹牛的內(nèi)容都是我干的芯丧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼世曾,長吁一口氣:“原來是場噩夢啊……” “哼注整!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起度硝,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肿轨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蕊程,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椒袍,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年藻茂,在試婚紗的時候發(fā)現(xiàn)自己被綠了驹暑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡辨赐,死狀恐怖优俘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掀序,我是刑警寧澤帆焕,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站不恭,受9級特大地震影響叶雹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜换吧,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一折晦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沾瓦,春花似錦满着、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乖篷,卻和暖如春响驴,著一層夾襖步出監(jiān)牢的瞬間透且,已是汗流浹背撕蔼。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工豁鲤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲸沮。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓琳骡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讼溺。 傳聞我的和親對象是個殘疾皇子楣号,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容