QUIC協議和HTTP3.0技術研究


1. 什么是QUIC

QUIC(Quick UDP Internet Connections)是Google開發的一種新的互聯網傳輸協議。

QUIC解決了現代Web應用程序遇到的許多傳輸層和應用層問題,而對應用程序編寫者幾乎沒有特殊的要求。QUIC與TCP+TLS+HTTP/2非常相似,但是它是在UDP之上實現的。因為現有協議受到遺留客戶端和中間包的阻礙,所以開發者可以將QUIC作為一個自包含的協議,進行現有協議無法實現的創新。

QUIC相對於TCP+TLS+HTTP/2的優勢包括:

  • 連接建立延遲低
  • 改進的擁塞控制
  • 無隊頭阻塞的多路復用
  • 前向糾錯
  • 連接遷移

1.1 連接建立延遲低

與TCP+TLS的1-3次往返相比,QUIC握手在發送有效載荷之前通常需要零次往返。

QUIC客戶端第一次連接到服務器時,客戶端必須執行一次往返握手,以獲取完成握手所需的信息。客戶端發送早期客戶端問候(CHLO),服務器發送拒絕報文(REJ),其中包含客戶端繼續工作所需的信息,包括源地址令牌和服務器的證書。客戶端下次發送CHLO時,可以使用以前連接中的緩存憑據來立即將加密的請求發送到服務器。

1.2 擁塞控制

QUIC具有可插拔的擁塞控制,並且向擁塞控制算法提供比TCP更豐富的信息。目前,Google在QUIC的實現中使用了TCP Cubic的重新實現,並且正在嘗試其他方法。

QUIC向擁塞控制算法提供的信息比TCP更豐富,比如QUIC原始和重傳的每個數據包都攜帶一個新的序列號。這樣,QUIC發送方就可以將重傳的ACK與原始的ACK區別開來,並且避免了TCP的重傳歧義問題。QUIC ACK還明確攜帶在收到數據包與發送確認之間的延遲,以及單調遞增的序列號,這樣可以精確地計算往返時間。

QUIC的ACK幀最多支持256個NACK范圍,因此QUIC對重新排序更具彈性,並且能夠在重新排序或丟包時保留更多字節。客戶端和服務器都可以更准確地了解對方已收到的數據包。

1.3 復用

TCP上的HTTP2有一個比較大的問題——隊頭阻塞問題。應用程序將TCP連接視為字節流。當TCP數據包丟失時,該HTTP2連接上的任何數據流都無法進行轉發,直到該數據包被遠端重新發送和接收為止。

QUIC重新設計了多路復用操作,丟失單個流數據的數據包通常只會影響該特定流。每個流幀都可以在到達時立即分派給該流,因此無損失的流可以繼續進行重組,在應用程序中繼續工作。

1.4 前向糾錯

為了從丟失的數據包中恢復而無需等待重傳,QUIC可以用FEC數據包來補充一組數據包。與RAID-4相似,FEC數據包包含FEC組中數據包的奇偶校驗。如果該組的一個數據包丟失,則可以從FEC數據包和該組中的其余數據包中恢復該數據包的內容。發送者可以決定是否發送FEC分組以優化特定場景。

1.5 連接遷移

QUIC連接由客戶端隨機生成的64位連接ID標識。TCP連接由4個元組的源地址、源端口、目標地址和目標端口標識。所以如果客戶端更改IP地址或端口,則任何活動的TCP連接將不再有效。當QUIC客戶端更改IP地址時,它可以繼續使用新IP地址中的舊連接ID,而不會中斷任何進行中的請求。

2. QUIC的生命周期

2.1 連接建立

QUIC的客戶端指的是發起連接的那個端。QUIC連接的建立是將編碼過的版本協商和握手傳輸合並在一起來降低連接建立的時延。接下來我們先描述版本協商。

從客戶端發往服務端的每一個初始化包都必須設置版本標志為1,並且必須指明所使用的協議的版本。客戶端發送的每一個包都必須設置版本標志為1,直到客戶端收到服務端發送的版本標志位0的包。服務端收到第一個版本標志位0的包之后,服務端會忽略所有(有可能是延時到達的)版本標志位1的包。

當服務端收到一個包含新連接的連接ID的包時,服務端會比較客戶端的版本以及服務端支持的版本。如果服務端接受了客戶端的版本,在整個連接的生命周期內,服務端會使用這個協議版本。在這種情況下,所有服務端發送的包都會把版本標志設置為0。

如果客戶端的版本不被服務端所接受,會引起一個1-RTT的時延。服務器會發送一個版本協商包給客戶端。這個包會設置版本標志為1並且包含了服務端所支持的所有版本。

當客戶端收到版本協商包,客戶端會選擇一個可接受的協議版本並且使用這個新版本重新發送所有的包。這些包必須設置版本標志為1並且包含新的經協商的協議版本。最終,客戶端接收到第一個常規包(換言之,不是協議協商包)表示版本協商結束,之后客戶端發送的所有后續包都將版本標志設置為0。

為了避免版本降級攻擊,客戶端在第一個包中所指定的協議版本和服務端所支持的版本集合必須被包含在加密握手數據中。客戶端需要驗證握手中的服務器版本列表和版本協商包中的版本列表相匹配。服務端需要驗證客戶端是否真的支持握手中的版本。

在連接建立的過程中,握手必須協商多種傳輸參數。目前已定義的傳輸參數會在文檔的稍后部分描述。

2.2 數據傳輸

QUIC實現了可靠連接,擁塞控制和流量控制。QUIC的流量控制近似遵循HTTP/2的流量控制。QUIC的可靠性和擁塞控制在一個伴生文檔中描述。一個QUIC連接使用一個獨立的包序號空間來同時實現擁塞控制和丟包恢復。

所有在QUIC連接上傳輸的數據,包括加密握手數據,都是作為流內的數據被發送,除了ACK包。

2.3 QUIC流的生命周期

流是雙向都擁有獨立序列封裝在流類型幀的數據。流可以被客戶端或服務端創建。多個流可以並行交替式的發送數據,流可以被取消。QUIC流的生命周期的模型類似於HTTP/2的[RFC7540]。

流的創建是通過在指定流上發送一個流類型幀隱式完成的。為了防止流ID碰撞,服務端初始化的流的流ID必須是偶數,客戶端初始化的流的流ID必須是奇數。0不是一個可用的流ID。流ID為1保留用於加密握手,應該是第一個客戶端初始化的流。如果使用HTTP/2 over QUIC,ID為3保留用於傳輸所有其它流的壓縮頭部,保證頭部按順序傳輸和處理。

新流創建時,連接的兩端的流ID都必須線性增長。比如ID為2的流可能在ID為3的流之后創建,但是ID為7的流必須不能在ID為9的流之后創建。對端可能收到亂序的流。比如,服務端先收到包序號為10從屬於流ID為9的幀,再收到包序號為9從屬於流ID為7的幀,服務器應該優雅處理這種情況。

如果一端收到了一條流的幀但是不想接收這條流,那么在這端可以立即回復一個RST_STREAM幀(下面有描述)。注意,初始化的那端可能已經在這條流上發數據了,接收端應該忽略這些數據。

如果一條流被創建了,它可以用來發送和接收數據。這意味着一系列的流類型幀數據可以被QUIC端在流上發送直到該方向上的流被關閉。

QUIC的任意一端都可以正常的關閉流。流被關閉有三種方式:

  1. 正常關閉
    由於流是雙工的,所以流可以半關閉或者全關閉。當流的一端發送了一個FIN標志為1的幀,流在這個方向進入半關閉狀態。FIN表示流上發送FIN的一方沒有更多數據要發送了。如果QUIC端既發送了FIN又接收了FIN,那么這個流被認為全關閉狀態。FIN應該在發送完流上的用戶數據后發送,FIN標志可以用一個空幀發送。
  2. 強制關閉
    客戶端和服務端都可以在任意時刻發送RST_STREAM幀。一個RST_STREAM幀包含了一個標識失敗原因的錯誤碼(錯誤碼列表在文檔后續部分展示)。如果是流發起者發送RST_STREAM幀,表示流發生了失敗並且沒有更多數據在流上發送。當流接收者發送RST_STREAM,那么發送端應該停止在這條流上發送任何數據。流接收端應該注意這里有一個競態條件,在發送端已經發送了的數據和RST_STREAM幀被接收到的時間之間。為了確保連接級別的流量控制被正確統計,即使發送端已經收到了RST_STREAM,發送端還必須確保流上的FIN和所有數據被對端接收了或者RST_STREAM已經被對端發送了。這也意味着RST_STREAM的發送者需要對這條流上的流類型幀保持回復(根據WINDOW_UPDATE)來確保發送端不會被流量控制所阻塞而觸發發送FIN。
  3. 連接關閉時,流也會被關閉

2.4 連接關閉

連接應該保持打開直到它們的空閑時間達到了之前協商的時間。當服務端決定關閉一個空閑連接,服務端不應該通知客戶端以避免喚醒手機設備。一個QUIC連接一旦被建立了,可以被兩種方式關閉:

  1. 顯式關閉
    一端發送一個CONNECTION_CLOSE幀給對端標識一個連接的關閉。在發送CONNECTION_CLOSE之前可能發送GOAWAY幀來標識這個連接很快會被關閉。發送一個GOAWAY幀用於通知對端目前所有活躍的流會繼續工作,但是GOAWAY的發送者不會再初始化任何其他的流並且不會再接收新的流。在活躍的流關閉時,可能會發送CONNECTION_CLOSE。如果一端發送了CONNECTION_CLOSE而還有未關閉的流是活躍的(一個或多個流上沒有FIN標志也沒有RST_STREAM幀被發送或接收),那么對端必須假設這種流是不完整的並且被異常關閉了。
  2. 隱式關閉
    QUIC連接的默認超時關閉時間是30秒,並且是在連接協商階段的一個必須的參數("ICSL")。最大值是10分鍾。如果在超時時間內沒有網絡活動,連接會被關閉。默認會發送一個CONNECTION_CLOSE幀。在發送顯式關閉代價太大的情況下(比如說移動網絡)可以開啟靜音關閉選項。

3. 編譯

一組示例server和client程序已經被包含在chromium源碼中,所以運行之前要拉取chromium源碼。

3.1 獲取depot_tools工具

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

depot_tools目錄加進環境變量(注意不要用~代替home目錄,否則后面步驟會運行出錯)

3.2 獲取chromium源碼

使用fetch --nohooks chromium命令獲取源碼,可以加參數--no-history加快速度(不獲取歷史版本記錄)。

3.3 安裝build所需的依賴

進入src文件夾,執行腳本文件install-build-deps.sh

之后執行gclient runhooks運行Chromium特定的hooks,它將下載其他二進制文件和其他可能需要的東西。

3.4 構建

Chromium源碼中包含了一組QUIC示例服務端與客戶端,創建構建目錄並使用ninja -C out/Debug quic_server quic_client構建。

4. 相關配置

下載www.example.org的副本,quic_server將在本地提供該副本。

獲取到的index.html需要修改,按官網要求修改其header。

  • 移除(如果存在):"Transfer-Encoding: chunked"
  • 移除(如果存在):"Alternate-Protocol: ..."
  • 添加:X-Original-Url: https://www.example.org/

運行服務器需要有效的證書和pkcs8格式的私鑰。使用以下腳本來生成。

cd net/tools/quic/certs
./generate-certs.sh
cd -

除了服務器的證書和公共密鑰,此腳本還將生成一個CA證書(net/tools/quic/certs/out/2048-sha256-root.pem),需要將其添加到OS的根證書存儲中以便在證書驗證期間將其信任。

安裝libnss3-tools工具

sudo apt-get install libnss3-tools

添加證書。

certutil -d sql:$HOME/.pki/nssdb/ -A -t "C,," -n 證書別名 -i 證書路徑

但從后面運行時的報錯可以得知,這一步沒有生效,暫時沒有解決這個問題。這里是官方導入證書的教程。

https://chromium.googlesource.com/chromium/src/+/master/docs/linux/cert_management.md

5. 運行測試

運行服務端程序,注意要指定端口。、

./out/Debug/quic_server \
  --quic_response_cache_dir=/tmp/quic-data/www.example.org \
  --certificate_file=net/tools/quic/certs/out/leaf_cert.pem \
  --key_file=net/tools/quic/certs/out/leaf_cert.pkcs8 \
  --host=127.0.0.1 \
  --port=6121

新開終端運行客戶端程序,因為我證書沒有設置好,直接運行會報錯,所以運行時要加上flag --disable_certificate_verification

./out/Debug/quic_client --host=127.0.0.1 --port=6121 https://www.example.org/ --disable_certificate_verification

正確獲取了之前下載的www.example.org

6. 部分源碼分析

這里使用libquic代碼進行分析。

6.1 數據包發送過程

QuicPacketCreator::SerializePacket這個函數對queued_frames_中的數據進行了序列化。而一次的封包操作,數據包的長度為max_plaintext_size_,具體大小不太清楚,總之,不超過kMaxPacketSize(1452)。

void QuicPacketCreator::SerializePacket(char* encrypted_buffer,
                                        size_t encrypted_buffer_len)
{
  size_t length = framer_->BuildDataPacket(header, queued_frames_,
                                           encrypted_buffer, packet_size_);
} 

疑惑的是,這里的queued_frames_中的所有幀的長度不超過max_plaintext_size_的大小的?發送的數據,要是超過了這個長度,多余的數據該存放在哪里呢?數據被緩存之后,什么時候會被再次發送出去呢?

從數據的發送開始,也就是ReliableQuicStream::WriteOrBufferData函數分析:

void ReliableQuicStream::WriteOrBufferData(
    StringPiece data,
    bool fin,
    QuicAckListenerInterface* ack_listener) {
      if (queued_data_.empty()) {
    struct iovec iov(MakeIovec(data));
    consumed_data = WritevData(&iov, 1, fin, ack_listener);
    DCHECK_LE(consumed_data.bytes_consumed, data.length());
  }

  // If there's unconsumed data or an unconsumed fin, queue it.
  //沒有寫完,則將剩余的數據緩寸,因為數據包的長度超出了kMaxPacketSize大小
  if (consumed_data.bytes_consumed < data.length() ||
      (fin && !consumed_data.fin_consumed)) {
    StringPiece remainder(data.substr(consumed_data.bytes_consumed));
    queued_data_bytes_ += remainder.size();
    queued_data_.emplace_back(remainder.as_string(), ack_listener);
  }
  }
  //當sent_packet_manager允許發送的時候,就是擁塞控制允許向外發送,緩存的數據被發送出去。
  void ReliableQuicStream::OnCanWrite(){
   while (!queued_data_.empty()) {
    PendingData* pending_data = &queued_data_.front();
    QuicAckListenerInterface* ack_listener = pending_data->ack_listener.get();
    if (queued_data_.size() == 1 && fin_buffered_) {
      fin = true;
    }
    if (pending_data->offset > 0 &&
        pending_data->offset >= pending_data->data.size()) {
      // This should be impossible because offset tracks the amount of
      // pending_data written thus far.
      QUIC_BUG << "Pending offset is beyond available data. offset: "
               << pending_data->offset << " vs: " << pending_data->data.size();
      return;
    }
    size_t remaining_len = pending_data->data.size() - pending_data->offset;
    struct iovec iov = {
        const_cast<char*>(pending_data->data.data()) + pending_data->offset,
        remaining_len};
    QuicConsumedData consumed_data = WritevData(&iov, 1, fin, ack_listener);
    queued_data_bytes_ -= consumed_data.bytes_consumed;
    if (consumed_data.bytes_consumed == remaining_len &&
        fin == consumed_data.fin_consumed) {
      queued_data_.pop_front();
    } else {
      if (consumed_data.bytes_consumed > 0) {
        pending_data->offset += consumed_data.bytes_consumed;
      }
      break;
    }
  } 
  }
  QuicConsumedData ReliableQuicStream::WritevData(
    const struct iovec* iov,
    int iov_count,
    bool fin,
    QuicAckListenerInterface* ack_listener) {
      QuicConsumedData consumed_data =
      WritevDataInner(QuicIOVector(iov, iov_count, write_length),
                      stream_bytes_written_, fin, ack_listener);
   }
   QuicConsumedData ReliableQuicStream::WritevDataInner(
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* ack_notifier_delegate) {
  return session()->WritevData(this, id(), iov, offset, fin,
                               ack_notifier_delegate);
}
QuicConsumedData QuicSession::WritevData(
    ReliableQuicStream* stream,
    QuicStreamId id,
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* ack_notifier_delegate) {
      QuicConsumedData data =
      connection_->SendStreamData(id, iov, offset, fin, ack_notifier_delegate);
    }
    QuicConsumedData QuicConnection::SendStreamData(
    QuicStreamId id,
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* listener){
      return packet_generator_.ConsumeData(id, iov, offset, fin, listener);
    }

上面顯示的兩個函數均有調用consumed_data = WritevData(&iov, 1, fin, ack_listener)。能保證寫進queued_frames_中的所有幀長不會超過一個向網絡中發送一個UDP數據包的長度的操作就是 packet_generator_.ConsumeData

QuicConsumedData QuicPacketGenerator::ConsumeData(
    QuicStreamId id,
    QuicIOVector iov,
    QuicStreamOffset offset,
    bool fin,
    QuicAckListenerInterface* listener){
    //當擁塞控制不允許寫的時候,或者數據包全部被Consume,才會退出
  while (delegate_->ShouldGeneratePacket(
      HAS_RETRANSMITTABLE_DATA, has_handshake ? IS_HANDSHAKE : NOT_HANDSHAKE)) {
    QuicFrame frame;
    if (!packet_creator_.ConsumeData(id, iov, total_bytes_consumed,
                                     offset + total_bytes_consumed, fin,
                                     has_handshake, &frame)) {
      // The creator is always flushed if there's not enough room for a new
      // stream frame before ConsumeData, so ConsumeData should always succeed.
      //因為下面調用了packet_creator_.Flush();,ConsumeData每次調用都會返回true
      QUIC_BUG << "Failed to ConsumeData, stream:" << id;
      return QuicConsumedData(0, false);
    }

    // A stream frame is created and added.
    size_t bytes_consumed = frame.stream_frame->data_length;
    if (listener != nullptr) {
      packet_creator_.AddAckListener(listener, bytes_consumed);
    }
    total_bytes_consumed += bytes_consumed;
    fin_consumed = fin && total_bytes_consumed == iov.total_length;
    DCHECK(total_bytes_consumed == iov.total_length ||
           (bytes_consumed > 0 && packet_creator_.HasPendingFrames()));

    if (!InBatchMode()) {
      packet_creator_.Flush();
    }

    if (total_bytes_consumed == iov.total_length) {
      // We're done writing the data. Exit the loop.
      // We don't make this a precondition because we could have 0 bytes of data
      // if we're simply writing a fin.
      break;
    }
    // TODO(ianswett): Move to having the creator flush itself when it's full.
    packet_creator_.Flush();
  }

  // Don't allow the handshake to be bundled with other retransmittable frames.
  if (has_handshake) {
    SendQueuedFrames(/*flush=*/true);
  }

  DCHECK(InBatchMode() || !packet_creator_.HasPendingFrames());
  return QuicConsumedData(total_bytes_consumed, fin_consumed);
   }
   bool QuicPacketCreator::ConsumeData(QuicStreamId id,
                                    QuicIOVector iov,
                                    size_t iov_offset,
                                    QuicStreamOffset offset,
                                    bool fin,
                                    bool needs_full_padding,
                                    QuicFrame* frame)
{
  CreateStreamFrame(id, iov, iov_offset, offset, fin, frame);
  if (!AddFrame(*frame, /*save_retransmittable_frames=*/true)) {
    // Fails if we try to write unencrypted stream data.
    delete frame->stream_frame;
    return false;
  }
}                         

Flush將數據發送到網絡中:

void QuicPacketCreator::Flush() {
  if (!HasPendingFrames()) {
    return;
  }

  // TODO(rtenneti): Change the default 64 alignas value (used the default
  // value from CACHELINE_SIZE).
  ALIGNAS(64) char seralized_packet_buffer[kMaxPacketSize];
  SerializePacket(seralized_packet_buffer, kMaxPacketSize);
  OnSerializedPacket();
}
void QuicConnection::OnSerializedPacket(SerializedPacket* serialized_packet) {
SendOrQueuePacket(serialized_packet);
}
void QuicConnection::SendOrQueuePacket(SerializedPacket* packet) {
  if (!queued_packets_.empty() || !WritePacket(packet)) {
    // Take ownership of the underlying encrypted packet.
    packet->encrypted_buffer = QuicUtils::CopyBuffer(*packet);
    queued_packets_.push_back(*packet);
    packet->retransmittable_frames.clear();
  }
}
bool QuicConnection::WritePacket(SerializedPacket* packet) {
  WriteResult result = writer_->WritePacket(
      packet->encrypted_buffer, encrypted_length, self_address().address(),
      peer_address(), per_packet_options_);
      //擁塞控制相關。
  bool reset_retransmission_alarm = sent_packet_manager_->OnPacketSent(
      packet, packet->original_path_id, packet->original_packet_number,
      packet_send_time, packet->transmission_type, IsRetransmittable(*packet));      
}

6.2 幀重傳部分

QUIC代碼里中的重傳邏輯,實現了兩種處理模式,一個是在connection層實現的重傳,另一個是在session層實現的重傳。在應用中只能啟用一個,要么有由connection負責重傳,要么由session負責重傳。

QuicFrame定義:

struct QUIC_EXPORT_PRIVATE QuicFrame {
  explicit QuicFrame(QuicStreamFrame stream_frame);
      struct {
      QuicFrameType type;

      // TODO(wub): These frames can also be inlined without increasing the size
      // of QuicFrame: QuicStopWaitingFrame, QuicRstStreamFrame,
      // QuicWindowUpdateFrame, QuicBlockedFrame, QuicPathResponseFrame,
      // QuicPathChallengeFrame and QuicStopSendingFrame.
      union {
        QuicAckFrame* ack_frame;
        QuicStopWaitingFrame* stop_waiting_frame;
        QuicRstStreamFrame* rst_stream_frame;
        QuicConnectionCloseFrame* connection_close_frame;
        QuicGoAwayFrame* goaway_frame;
        QuicWindowUpdateFrame* window_update_frame;
        QuicBlockedFrame* blocked_frame;
        QuicApplicationCloseFrame* application_close_frame;
        QuicNewConnectionIdFrame* new_connection_id_frame;
        QuicRetireConnectionIdFrame* retire_connection_id_frame;
        QuicPathResponseFrame* path_response_frame;
        QuicPathChallengeFrame* path_challenge_frame;
        QuicStopSendingFrame* stop_sending_frame;
        QuicMessageFrame* message_frame;
        QuicCryptoFrame* crypto_frame;
        QuicNewTokenFrame* new_token_frame;
      };
    };
}

數據重傳首先要判斷數據的丟包:

bool QuicSentPacketManager::OnAckFrameEnd(QuicTime ack_receive_time){
  PostProcessAfterMarkingPacketHandled(last_ack_frame_, ack_receive_time,
                                       rtt_updated_, prior_bytes_in_flight);
}
void QuicSentPacketManager::PostProcessAfterMarkingPacketHandled(
    const QuicAckFrame& ack_frame,
    QuicTime ack_receive_time,
    bool rtt_updated,
    QuicByteCount prior_bytes_in_flight){
     InvokeLossDetection(ack_receive_time);
    }
void QuicSentPacketManager::InvokeLossDetection(QuicTime time) {
	MarkForRetransmission(packet.packet_number, LOSS_RETRANSMISSION);    
 }
 //在這里就進入分叉處理,
void QuicSentPacketManager::MarkForRetransmission(
    QuicPacketNumber packet_number,
    TransmissionType transmission_type) {
  //  記錄要重傳的數據包序號,后續connection層的重傳會用到
  if (!session_decides_what_to_write()) {
    if (!unacked_packets_.HasRetransmittableFrames(*transmission_info)) {
      return;
    }
    if (!QuicContainsKey(pending_retransmissions_, packet_number)) {
      pending_retransmissions_[packet_number] = transmission_type;
    }
    return;
  }
//如果session_decides_what_to_write_開啟,則由session負責重傳。
  HandleRetransmission(transmission_type, transmission_info);    
    }

6.2.1 connection負責的重傳

void QuicConnection::WritePendingRetransmissions()
{
  DCHECK(!session_decides_what_to_write());
  // Keep writing as long as there's a pending retransmission which can be
  // written.
  while (sent_packet_manager_.HasPendingRetransmissions() &&
         CanWrite(HAS_RETRANSMITTABLE_DATA)) {
    const QuicPendingRetransmission pending =
        sent_packet_manager_.NextPendingRetransmission();

    // Re-packetize the frames with a new packet number for retransmission.
    // Retransmitted packets use the same packet number length as the
    // original.
    // Flush the packet generator before making a new packet.
    // TODO(ianswett): Implement ReserializeAllFrames as a separate path that
    // does not require the creator to be flushed.
    // TODO(fayang): FlushAllQueuedFrames should only be called once, and should
    // be moved outside of the loop. Also, CanWrite is not checked after the
    // generator is flushed.
    {
      ScopedPacketFlusher flusher(this, NO_ACK);
      packet_generator_.FlushAllQueuedFrames();
    }
    DCHECK(!packet_generator_.HasQueuedFrames());
    char buffer[kMaxPacketSize];
    packet_generator_.ReserializeAllFrames(pending, buffer, kMaxPacketSize);
  }
}
void QuicPacketCreator::ReserializeAllFrames(
    const QuicPendingRetransmission& retransmission,
    char* buffer,
    size_t buffer_len) {
   SerializePacket(buffer, buffer_len);
  packet_.original_packet_number = retransmission.packet_number;   
   }

這里最終還是去stream中的send_buffer_獲取數據。packet_.original_packet_number記錄數據包上次發送使用的序列號,下次發送的時候回調用QuicSentPacketManager::OnPacketSent將原來記錄的丟失幀信息更新。

bool QuicSentPacketManager::OnPacketSent(
    SerializedPacket* serialized_packet,
    QuicPacketNumber original_packet_number,
    QuicTime sent_time,
    TransmissionType transmission_type,
    HasRetransmittableData has_retransmittable_data) {
    unacked_packets_.AddSentPacket(serialized_packet, original_packet_number,
                                 transmission_type, sent_time, in_flight);  
    }
    void QuicUnackedPacketMap::AddSentPacket(SerializedPacket* packet,
                                         QuicPacketNumber old_packet_number,
                                         TransmissionType transmission_type,
                                         QuicTime sent_time,
                                         bool set_in_flight){
  if (old_packet_number.IsInitialized()) {
    TransferRetransmissionInfo(old_packet_number, packet_number,
                               transmission_type, &info);
  }  
}

6.2.2 session負責的重傳

在session層實現的重傳,就不需要sent_packet_manager_.NextPendingRetransmission()獲取pending中含有的可重傳幀的原理的傳輸序號了(retransmission.packet_number)。也可以說QuicUnackedPacketMap中的記錄的unacked_packets_信息就不太重要了。

void QuicSentPacketManager::HandleRetransmission(
    TransmissionType transmission_type,
    QuicTransmissionInfo* transmission_info) {
   unacked_packets_.NotifyFramesLost(*transmission_info, transmission_type); 
    }
    
void QuicUnackedPacketMap::NotifyFramesLost(const QuicTransmissionInfo& info,
                                            TransmissionType type) {
  DCHECK(session_decides_what_to_write_);
  for (const QuicFrame& frame : info.retransmittable_frames) {
    session_notifier_->OnFrameLost(frame);
  }
}

void QuicSession::OnFrameLost(const QuicFrame& frame){
  QuicStream* stream = GetStream(frame.stream_frame.stream_id);
  if (stream == nullptr) {
    return;
  }
  stream->OnStreamFrameLost(frame.stream_frame.offset,
                            frame.stream_frame.data_length,
                            frame.stream_frame.fin);
}
void QuicStream::OnStreamFrameLost(QuicStreamOffset offset,
                                   QuicByteCount data_length,
                                   bool fin_lost) {
  if (data_length > 0) {
    send_buffer_.OnStreamDataLost(offset, data_length);
  }
}
void QuicStreamSendBuffer::OnStreamDataLost(QuicStreamOffset offset,
                                            QuicByteCount data_length) {
  for (const auto& lost : bytes_lost) {
    pending_retransmissions_.Add(lost.min(), lost.max());
  }
}

再次發送數據的邏輯:

void QuicStream::OnCanWrite() {
  if (HasPendingRetransmission()) {
    WritePendingRetransmission();
    // Exit early to allow other streams to write pending retransmissions if
    // any.
    return;
  }
}
void QuicStream::WritePendingRetransmission() {
      consumed = session()->WritevData(this, id_, pending.length, pending.offset,
                                can_bundle_fin ? FIN : NO_FIN);
}

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM