天天看點

UDP實作可靠傳輸:QUIC

UDP實作可靠傳輸:

把TCP可靠傳輸的特性(序号,确認應答,逾時重傳,流量控制)在應用層實作一遍。

為什麼不用天然支援可靠傳輸的TCP:

  • 更新TCP很困難(TCP在運輸層,更新TCP需要更新作業系統)。
  • TCP建立連接配接的延遲。
  • TCP存在視窗阻塞的問題。
  • 網絡遷移需要TCP重建立立連接配接。

現在市面上已經有基于UDP實作可靠傳輸的成熟方案了,即QUIC協定,已經應用在了HTTP/3。

在HTTP/3中,UDP報頭與HTTP消息間有三層頭部:

UDP實作可靠傳輸:QUIC

Packet Header:

Packet Header首次建立連接配接時和日常傳輸資料時使用的Header是不同的:

UDP實作可靠傳輸:QUIC

Long Packet Header用于首次建立連接配接,Short Packet Header用于日常傳輸資料。

伺服器根據用戶端的源連接配接 ID 建立連接配接,這樣後續傳輸時,雙方隻需要固定住目的連接配接ID,進而實作連接配接遷移功能。是以日常傳輸資料的 Short Packet Header 不需要在傳輸源連接配接 ID 了。

Short Packet Header 中的 Packet Number 是每個封包獨一無二的編号,它是嚴格遞增的,也就是說就算 Packet N 丢失了,重傳的 Packet N 的 Packet Number 已經不是 N,而是一個比 N 大的值。

UDP實作可靠傳輸:QUIC

TCP 在重傳封包時的序号和原始封包的序号是一樣的,這導緻了 TCP 重傳的歧義問題。

UDP實作可靠傳輸:QUIC

比如上圖,當 TCP 發生逾時重傳後,用戶端發起重傳,然後接收到了服務端确認 ACK 。由于用戶端原始封包和重傳封包序列号都是一樣的,那麼服務端針對這兩個封包回複的都是相同的 ACK,用戶端就無法判斷出是原始封包的響應還是重傳封包的響應,這樣在計算 RTT(往返時間) 時就會産生歧義。(應該選擇從發送原始封包開始計算,還是重傳原始封包開始計算呢?)

RTT 計算不精确的話,RTO(逾時時間)也就不精确,因為 RTO 是基于 RTT 來計算的,RTO 計算不準确可能導緻重傳的機率事件增大。

QUIC 封包中的 Pakcet Number 是嚴格遞增的, 即使是重傳封包,它的 Pakcet Number 也是遞增的,這樣就能更加精确計算出封包的 RTT。

UDP實作可靠傳輸:QUIC

另外,還有一個好處,QUIC 使用的 Packet Number 單調遞增的設計,可以讓資料包不再像TCP 那樣必須有序确認,QUIC 支援亂序确認,當資料包Packet N 丢失後,隻要有新的已接收資料包确認,目前視窗就會繼續向右滑動。

待發送端超過一定時間沒收到 Packet N 的确認封包後,會将需要重傳的資料包放到待發送隊列,重新編号比如資料包 Packet N+M 後重新發送給接收端,對重傳資料包的處理跟發送新的資料包類似,這樣就不會因為丢包重傳将目前視窗阻塞在原地,進而解決了視窗阻塞問題。

是以,Packet Number 單調遞增的兩個好處:

  • 可以更加精确計算 RTT,沒有 TCP 重傳的歧義性問題;
  • 可以支援亂序确認,防止因為丢包重傳将目前視窗阻塞在原地,而 TCP 必須是順序确認的,丢包時會導緻視窗阻塞。

QUIC Frame Header:

一個Packet封包中可以存放多個QUIC Frame:

UDP實作可靠傳輸:QUIC

每一個 Frame 都有明确的類型,這裡隻舉例 Stream 類型的 Frame 格式,Stream 可以認為就是一條 HTTP 請求,它長這樣:

UDP實作可靠傳輸:QUIC
  • Stream ID 作用:多個并發傳輸的 HTTP 消息,通過不同的 Stream ID 加以差別;
  • Offset 作用:類似于 TCP 協定中的 Seq 序号,保證資料的順序性和可靠性;
  • Length 作用:指明了 Frame 資料的長度

在前面介紹 Packet Header 時,說到 Packet Number 是嚴格遞增,即使重傳封包的 Packet Number 也是遞增的,既然重傳資料包的 Packet N+M 與丢失資料包的 Packet N 編号并不一緻,我們怎麼确定這兩個資料包的内容一樣呢?

是以引入 Frame Header 這一層,通過 Stream ID + Offset 字段資訊實作資料的有序性,通過比較兩個資料包的 Stream ID 與 Stream Offset ,如果都是一緻,就說明這兩個資料包的内容一緻。

舉個例子,下圖中,資料包 Packet N 丢失了,後面重傳該資料包的編号為 Packet N+2,丢失的資料包和重傳的資料包 Stream ID 與 Offset 都一緻,說明這兩個資料包的内容一緻。這些資料包傳輸到接收端後,接收端能根據 Stream ID 與 Offset 字段資訊将 Stream x 和 Stream x+y 按照順序組織起來,然後交給應用程式處理。

UDP實作可靠傳輸:QUIC

總的來說,QUIC 通過單向遞增的 Packet Number,配合 Stream ID 與 Offset 字段資訊,可以支援亂序确認而不影響資料包的正确組裝,擺脫了TCP 必須按順序确認應答 ACK 的限制,解決了 TCP 因某個資料包重傳而阻塞後續所有待發送資料包的問題。

QUIC是如何解決隊頭阻塞問題的:

TCP視窗阻塞問題:

TCP 發送出去的資料,都是需要按序确認的,隻有在前面資料都被确認後,發送視窗才會滑動。如果前面某個資料沒有被确認,會導緻發送視窗不能繼續移動,這時就無法再發送新的資料,隻能逾時重傳這個資料,直到收到這個重傳資料的确認,發送視窗才能移動,繼續發送後面的資料。

HTTP/2視窗阻塞問題:

HTTP/2抽象出流的概念,實作HTTP的并發傳輸,一個流就代表HTTP/1.1裡的請求和響應。

UDP實作可靠傳輸:QUIC

在HTTP/2連接配接上,不同流的幀是可以亂序發送的,因為每個幀的頭部會攜帶stream ID資訊,是以接收端可以将同一個stream的幀組裝成HTTP消息,而同一stream内部的幀必須是嚴格有序的。

但是HTTP/2的各個流都是在一條TCP連接配接上傳輸,這意味着多個流共用一個TCP滑動視窗,目前面資料沒有被确認,滑動視窗就無法向前移動,此時就會阻塞所有的HTTP請求。

UDP實作可靠傳輸:QUIC

沒有視窗阻塞的QUIC:

QUIC也借鑒了HTTP/2裡流的概念,在一條QUIC連接配接上并發發送多個HTTP請求(stream)。

但是QUIC給每一個流都配置設定了一個獨立的滑動視窗,這樣使得各個流之間沒有依賴關系,都是互相獨立的,各自控制滑動視窗。

假如某一個流丢了一個資料包,也隻會阻塞這一個流的視窗,不會影響其他流。

UDP實作可靠傳輸:QUIC

QUIC流量控制:

TCP流量控制:

接收方告訴發送方自己的接收視窗大小,發送方據此調整自己發送的資料量。

QUIC實作了兩種級别的流量控制:

stream級别的流量控制:每個流都有獨立的滑動視窗,每個流都可以做流量控制,防止單個流消耗掉連接配接的全部接收視窗。

connection流量控制:限制連接配接中所有流的發送資料量,防止其超過各個流接收視窗大小之和。

參考:

​​https://blog.csdn.net/m0_69305074/article/details/124753240​​

繼續閱讀