UDP實作可靠傳輸:
把TCP可靠傳輸的特性(序号,确認應答,逾時重傳,流量控制)在應用層實作一遍。
為什麼不用天然支援可靠傳輸的TCP:
- 更新TCP很困難(TCP在運輸層,更新TCP需要更新作業系統)。
- TCP建立連接配接的延遲。
- TCP存在視窗阻塞的問題。
- 網絡遷移需要TCP重建立立連接配接。
現在市面上已經有基于UDP實作可靠傳輸的成熟方案了,即QUIC協定,已經應用在了HTTP/3。
在HTTP/3中,UDP報頭與HTTP消息間有三層頭部:

Packet Header:
Packet Header首次建立連接配接時和日常傳輸資料時使用的Header是不同的:
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 大的值。
TCP 在重傳封包時的序号和原始封包的序号是一樣的,這導緻了 TCP 重傳的歧義問題。
比如上圖,當 TCP 發生逾時重傳後,用戶端發起重傳,然後接收到了服務端确認 ACK 。由于用戶端原始封包和重傳封包序列号都是一樣的,那麼服務端針對這兩個封包回複的都是相同的 ACK,用戶端就無法判斷出是原始封包的響應還是重傳封包的響應,這樣在計算 RTT(往返時間) 時就會産生歧義。(應該選擇從發送原始封包開始計算,還是重傳原始封包開始計算呢?)
RTT 計算不精确的話,RTO(逾時時間)也就不精确,因為 RTO 是基于 RTT 來計算的,RTO 計算不準确可能導緻重傳的機率事件增大。
QUIC 封包中的 Pakcet Number 是嚴格遞增的, 即使是重傳封包,它的 Pakcet Number 也是遞增的,這樣就能更加精确計算出封包的 RTT。
另外,還有一個好處,QUIC 使用的 Packet Number 單調遞增的設計,可以讓資料包不再像TCP 那樣必須有序确認,QUIC 支援亂序确認,當資料包Packet N 丢失後,隻要有新的已接收資料包确認,目前視窗就會繼續向右滑動。
待發送端超過一定時間沒收到 Packet N 的确認封包後,會将需要重傳的資料包放到待發送隊列,重新編号比如資料包 Packet N+M 後重新發送給接收端,對重傳資料包的處理跟發送新的資料包類似,這樣就不會因為丢包重傳将目前視窗阻塞在原地,進而解決了視窗阻塞問題。
是以,Packet Number 單調遞增的兩個好處:
- 可以更加精确計算 RTT,沒有 TCP 重傳的歧義性問題;
- 可以支援亂序确認,防止因為丢包重傳将目前視窗阻塞在原地,而 TCP 必須是順序确認的,丢包時會導緻視窗阻塞。
QUIC Frame Header:
一個Packet封包中可以存放多個QUIC Frame:
每一個 Frame 都有明确的類型,這裡隻舉例 Stream 類型的 Frame 格式,Stream 可以認為就是一條 HTTP 請求,它長這樣:
- 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 按照順序組織起來,然後交給應用程式處理。
總的來說,QUIC 通過單向遞增的 Packet Number,配合 Stream ID 與 Offset 字段資訊,可以支援亂序确認而不影響資料包的正确組裝,擺脫了TCP 必須按順序确認應答 ACK 的限制,解決了 TCP 因某個資料包重傳而阻塞後續所有待發送資料包的問題。
QUIC是如何解決隊頭阻塞問題的:
TCP視窗阻塞問題:
TCP 發送出去的資料,都是需要按序确認的,隻有在前面資料都被确認後,發送視窗才會滑動。如果前面某個資料沒有被确認,會導緻發送視窗不能繼續移動,這時就無法再發送新的資料,隻能逾時重傳這個資料,直到收到這個重傳資料的确認,發送視窗才能移動,繼續發送後面的資料。
HTTP/2視窗阻塞問題:
HTTP/2抽象出流的概念,實作HTTP的并發傳輸,一個流就代表HTTP/1.1裡的請求和響應。
在HTTP/2連接配接上,不同流的幀是可以亂序發送的,因為每個幀的頭部會攜帶stream ID資訊,是以接收端可以将同一個stream的幀組裝成HTTP消息,而同一stream内部的幀必須是嚴格有序的。
但是HTTP/2的各個流都是在一條TCP連接配接上傳輸,這意味着多個流共用一個TCP滑動視窗,目前面資料沒有被确認,滑動視窗就無法向前移動,此時就會阻塞所有的HTTP請求。
沒有視窗阻塞的QUIC:
QUIC也借鑒了HTTP/2裡流的概念,在一條QUIC連接配接上并發發送多個HTTP請求(stream)。
但是QUIC給每一個流都配置設定了一個獨立的滑動視窗,這樣使得各個流之間沒有依賴關系,都是互相獨立的,各自控制滑動視窗。
假如某一個流丢了一個資料包,也隻會阻塞這一個流的視窗,不會影響其他流。
QUIC流量控制:
TCP流量控制:
接收方告訴發送方自己的接收視窗大小,發送方據此調整自己發送的資料量。
QUIC實作了兩種級别的流量控制:
stream級别的流量控制:每個流都有獨立的滑動視窗,每個流都可以做流量控制,防止單個流消耗掉連接配接的全部接收視窗。
connection流量控制:限制連接配接中所有流的發送資料量,防止其超過各個流接收視窗大小之和。
參考:
https://blog.csdn.net/m0_69305074/article/details/124753240