TCP 隊頭阻塞的主要原因是數(shù)據(jù)包超時確認(rèn)或丟失阻塞了當(dāng)前窗口向右滑動,我們最容易想到的解決隊頭阻塞的方案是不讓超時確認(rèn)或丟失的數(shù)據(jù)包將當(dāng)前窗口阻塞在原地。QUIC (Quick UDP Internet Connections)也正是采用上述方案來解決TCP 隊頭阻塞問題的。
TCP 為了保證可靠性,使用了基于字節(jié)序號的 Sequence Number 及 Ack 來確認(rèn)消息的有序到達。QUIC 同樣是一個可靠的協(xié)議,它使用 Packet Number 代替了 TCP 的 Sequence Number,并且每個 Packet Number 都嚴(yán)格遞增,也就是說就算 Packet N 丟失了,重傳的 Packet N 的 Packet Number 已經(jīng)不是 N,而是一個比 N 大的值,比如Packet N M。
QUIC 使用的Packet Number 單調(diào)遞增的設(shè)計,可以讓數(shù)據(jù)包不再像TCP 那樣必須有序確認(rèn),QUIC 支持亂序確認(rèn),當(dāng)數(shù)據(jù)包Packet N 丟失后,只要有新的已接收數(shù)據(jù)包確認(rèn),當(dāng)前窗口就會繼續(xù)向右滑動。 待發(fā)送端獲知數(shù)據(jù)包Packet N 丟失后,會將需要重傳的數(shù)據(jù)包放到待發(fā)送隊列,重新編號比如數(shù)據(jù)包Packet N M 后重新發(fā)送給接收端,對重傳數(shù)據(jù)包的處理跟發(fā)送新的數(shù)據(jù)包類似,這樣就不會因為丟包重傳將當(dāng)前窗口阻塞在原地,從而解決了隊頭阻塞問題。那么,既然重傳數(shù)據(jù)包的Packet N M 與丟失數(shù)據(jù)包的Packet N 編號并不一致,我們怎么確定這兩個數(shù)據(jù)包的內(nèi)容一樣呢? 還記得前篇博文:HTTP/2 是如何解決HTTP/1.1 性能瓶頸的?使用Stream ID 來標(biāo)識當(dāng)前數(shù)據(jù)流屬于哪個資源請求,這同時也是數(shù)據(jù)包多路復(fù)用傳輸?shù)浇邮斩撕竽苷=M裝的依據(jù)。重傳的數(shù)據(jù)包Packet N M 和丟失的數(shù)據(jù)包Packet N 單靠Stream ID 的比對一致仍然不能判斷兩個數(shù)據(jù)包內(nèi)容一致,還需要再新增一個字段Stream Offset,標(biāo)識當(dāng)前數(shù)據(jù)包在當(dāng)前Stream ID 中的字節(jié)偏移量。
有了Stream Offset 字段信息,屬于同一個Stream ID 的數(shù)據(jù)包也可以亂序傳輸了(HTTP/2 中僅靠Stream ID 標(biāo)識,要求同屬于一個Stream ID 的數(shù)據(jù)幀必須有序傳輸),通過兩個數(shù)據(jù)包的Stream ID 與 Stream Offset 都一致,就說明這兩個數(shù)據(jù)包的內(nèi)容一致。
上圖中數(shù)據(jù)包Packet N 丟失了,后面重傳該數(shù)據(jù)包的編號為Packet N 2,丟失的數(shù)據(jù)包和重傳的數(shù)據(jù)包Stream ID 與 Offset 都一致,說明這兩個數(shù)據(jù)包的內(nèi)容一致。這些數(shù)據(jù)包傳輸?shù)浇邮斩撕?,接收端能根?jù)Stream ID 與 Offset 字段信息正確組裝成完整的資源。
QUIC 通過單向遞增的Packet Number,配合Stream ID 與 Offset 字段信息,可以支持非連續(xù)確認(rèn)應(yīng)答Ack而不影響數(shù)據(jù)包的正確組裝,擺脫了TCP 必須按順序確認(rèn)應(yīng)答Ack 的限制(也即不能出現(xiàn)非連續(xù)的空位),解決了TCP 因某個數(shù)據(jù)包重傳而阻塞后續(xù)所有待發(fā)送數(shù)據(jù)包的問題(也即隊頭阻塞問題)。
QUIC 解決了TCP 的隊頭阻塞問題,同時繼承了HTTP/2 的多路復(fù)用優(yōu)點,因為Stream Offset 字段的引入,QUIC 中同一Stream ID 的數(shù)據(jù)幀也支持亂序傳輸,不再像HTTP/2 要求的同一Stream ID 的數(shù)據(jù)幀必須有序傳輸那么嚴(yán)格。
從上面QUIC 的數(shù)據(jù)包結(jié)構(gòu)中可以看出,同一個Connection ID 可以同時傳輸多個Stream ID,由于QUIC 支持非連續(xù)的Packet Number 確認(rèn),某個Packet N 超時確認(rèn)或丟失,不會影響其它未包含在該數(shù)據(jù)包中的Stream Frame 的正常傳輸。
同一個Packet Number 可承載多個Stream Frame,若該數(shù)據(jù)包丟失,則其承載的Stream Frame 都需要重新傳輸。因為同一Stream ID 的數(shù)據(jù)幀亂序傳輸后也能正確組裝,這些需要重傳的Stream Frame 并不會影響其它待發(fā)送Stream Frame 的正常傳輸。
TCP 建立連接的三次握手過程都做了哪些工作呢?首先確認(rèn)雙方是否能正常收發(fā)數(shù)據(jù),通信雙方交換待發(fā)送數(shù)據(jù)的初始序列編號并作為有序確認(rèn)應(yīng)答的基點,通信雙方根據(jù)預(yù)設(shè)的狀態(tài)轉(zhuǎn)換圖完成各自的狀態(tài)遷移過程,通信雙方為分組數(shù)據(jù)的可靠傳輸和狀態(tài)信息的記錄管理分配控制塊緩存資源等。下面給出TCP 連接建立、數(shù)據(jù)傳輸、連接釋放三個階段的報文交互過程和狀態(tài)遷移圖示(詳見博文:TCP協(xié)議與Transmission Control Protocol):
到了移動互聯(lián)網(wǎng)時代,客戶端(比如手機)的位置可能一直在變,接入不同的基站可能就會被分配不同的Source IP 和Source Port。即便在家里,客戶端可能也需要在LTE 和WIFI 之間切換,這兩個網(wǎng)絡(luò)分配給客戶端的Source IP 和Source Port 可能也是不同的。
早期移動電話使用Mobile IP 技術(shù)來解決網(wǎng)絡(luò)遷移或切換過程引起的斷連問題,Mobile IP 主要是通過新建IP 隧道的方式(也即建立一個新連接來轉(zhuǎn)發(fā)數(shù)據(jù)包)保持原來的連接不斷開,但這種方式增加了數(shù)據(jù)包的傳輸路徑,也就增大了數(shù)據(jù)包的往返時間,降低了數(shù)據(jù)包的傳輸效率。Mobile IP 的工作原理如下(移動主機遷移到外部代理后,為了保持原連接不斷開,新建了一條到歸屬代理的IP 隧道,讓歸屬代理以原主機IP 轉(zhuǎn)發(fā)數(shù)據(jù)包):
QUIC 擺脫了TCP 的諸多限制,可以重新設(shè)計連接標(biāo)識,還記得前面給出的QUIC 數(shù)據(jù)包結(jié)構(gòu)嗎?QUIC 數(shù)據(jù)包結(jié)構(gòu)中有一個Connection ID 字段專門標(biāo)識連接,Connection ID 是一個64位的通用唯一標(biāo)識UUID (Universally Unique IDentifier)。
借助Connection ID,QUIC 的連接不再綁定IP 與 Port 信息,即便因為網(wǎng)絡(luò)遷移或切換導(dǎo)致Source IP 和Source Port 發(fā)生變化,只要Connection ID 不變就仍是同一個連接,協(xié)議層只需要將控制塊中記錄的Source IP 和Source Port 信息更新即可,不需要像TCP 那樣先斷開連接,這就可以保證連接的順暢遷移或切換,用戶基本不會感知到連接切換過程。