文章基調
- 不是科普類文章,不是科普 http2 功能的文章
- 記錄 http2 中難以理解的點,系作者在學習 http2 時的困惑,已經最終的理解
是個人的理解,可能有不嚴謹的地方,歡迎討論
如何理解 TCP 分幀 與 http2 分幀 的區別
- 假設「傳輸完整的資料」是「運輸一個訂單貨物」,每「訂單中的一個貨物」佔滿「一個貨車廂」
TCP 位於傳輸層,可以理解為運輸的貨車
- TCP 中的每一幀都是有序的,按發車時間標記趟次,第一趟次,第二趟次,第三趟次,第 N 趟次
- TCP 可以同時發若干輛車,假設 4 輛車,則一次發車就有,第一趟次,第二趟次,第三趟次,第四趟次
- 每輛車有些提前到達,有些很慢才到,不一定按照發車的順序到達目的地
- 當其中一個趟次的車回來了,才發下一趟車次。
- 趟次的作用,是為了貨品送到目的地的時候可以重新按順序排列,可以將每趟次的貨理解為高達模型的零件(一車只運送一個零件),有順序,才能識別重新拼裝起來
- 將一個訂單中的貨,分別通過不同的趟次運輸,就是「TCP 二進位制分幀」,將訂單的貨(完整的資料)分成每一車(幀)進行運輸
HTTP 處於應用層,一個 http 請求及響應,可以理解為下訂單(請求)購買一批貨(響應)的過程,(注意是一批,有若干個貨物)而這批貨並沒有貨品名稱(無法對應這批貨對應的是哪個訂單,無法將響應與請求關聯起來)
- 這時 http 還沒有分幀化,粒度是以訂單為單位,一個訂單就是一個 http
- 將一個 TCP 連結理解為一個運輸合同
http0.9,1.0
【問題】每一次訂單都籤一次運輸合同,很麻煩- 籤一次運輸合同(三次握手)
- 下訂單(請求)
- 生產貨物(伺服器計算結果)
- 發起運輸(TCP 傳輸內容)
- 收貨物(響應)
- 結一次運輸合同的賬(四次揮手)
- 籤一次運輸合同(三次握手)
- 下訂單(請求)
- 生產貨物(伺服器計算結果)
- 發起運輸(TCP 傳輸內容)
- 收貨物(響應)
- 結一次運輸合同的賬(四次揮手)
http1.1 keep-alive
【改進】運輸合同改成月結,複用 TCP 連結
【問題】工廠無法同時生產多個訂單的貨物,需要上一個訂單收貨
- 籤一次運輸合同(三次握手)
- 下訂單(請求)
- 生產貨物(伺服器計算結果)
- 發起運輸(傳輸內容)
- 收貨物(響應)
- 下訂單(請求)
- 生產貨物(伺服器計算結果)
- 發起運輸(傳輸內容)
- 收貨物(響應)
- 結一次運輸合同的賬(四次揮手)
http1.1 pipeline
【改進】可以同時發多個訂單了,通過收貨順序,來識別貨物對應的是那一次訂單的內容
【改進】工廠可以同時生產多個訂單的貨
【問題】訂單存在依賴關係,即使第二次訂單的貨物生產好了,也得等第一次訂單的貨物生產好並全部傳輸完,才能發貨。否則會被當做第一個訂單的貨
- 籤一次運輸合同(三次握手)
- 下訂單(第一個請求)
- 下訂單(第二個請求)
- 同時生產第一個批和第二批貨物(伺服器計算結果)
- 按順序發第一個訂單的運輸(傳輸內容)
- 按順序收貨物(響應,對應第一個響應)
- 按順序發第二個訂單的運輸(傳輸內容)
- 收貨物(響應,對應第二個響應)
- 結一次運輸合同的賬(四次揮手)
http2
【改進】將一個訂單的貨拆分成多個批次,為每個批次標識上是哪個訂單的貨
【改進】由於能識別一批次貨品所屬訂單,最小粒度從一個訂單的貨,改為一批次的。原本按訂單運輸,現在改為按批次運輸,這個最小顆粒度的變化就是 http2 分幀:將一個響應或請求拆分成多個分幀片段
【改進】最小粒度變成了批次,生產完一批次的貨品,就可以馬上傳輸,不需要等整個訂單的貨全部生產完才傳輸
- 籤一次運輸合同(三次握手)
- 下訂單(第一個請求)
- 下訂單(第二個請求)
- 同時生產第一個訂單和第二訂單的貨物(伺服器計算)
- 每生產批次貨物,就給這批次的貨打上標籤,標識是哪個訂單的貨(分幀)
- 準備好了一批次貨物,就發運輸,不需要管是哪個訂單的(傳輸)
- 收貨,重新分揀是哪個訂單的貨(根據分幀標識對應是那一次請求的響應)
- 每生產批次貨物,就給這批次的貨打上標籤,標識是哪個訂單的貨(分幀)
- 準備好了一批次貨物,就發運輸,不需要管是哪個訂單的(傳輸)
- 收貨,重新分揀是哪個訂單的貨(根據分幀標識對應是那一次請求的響應)
- 直到所有貨物都傳輸完成
- 結一次運輸合同的賬(四次揮手)
總結
- 可以看出,tcp 的分幀與 http2 的分幀是不同維度的區分
- tcp 的分幀維度是一個趟次運輸(一個趟次運輸一個貨物),http2 的分幀維度是 一批貨物(可能是一個貨物,也可能是若干個貨物)
- http2 分幀的本質是將原本一個訂單(一個請求或響應),拆分成多個批次(多個幀),縮小資料顆粒度,增加靈活性
參考資料
- https://segmentfault.com/q/1010000005167289
- https://blog.csdn.net/u598975767/article/details/112788129
- https://blog.wangriyu.wang/2018/05-HTTP2.html
為什麼要分幀
本質上,只要給一個訂單的貨(響應)打上訂單(請求)標識,就可以標識是哪個訂單的,就可以解決先訂單依賴的問題(後一個訂單不需要等前一個訂單傳輸完),為什麼需要將訂單拆散為批次呢?
- 車的數量(TCP 通道寬度,流量寬度)是有限的,拆散了並不是加快運輸過程
- 拆成批次(分幀)是為了從根源支援資料的分配傳輸,如果一個訂單的量很大,按訂單發貨,就得等整個訂單的貨生產完成才能運輸。而拆成批次(分幀)就可以將粒度減低,生產一個批次就運輸一個批次,不需要等整個訂單的貨生產完成。
如何理解 http1.1 是文字協議,http2 是二進位制協議
- stackoverflow 中對應的討論,對應國內論壇
文字協議,資訊傳輸過程經歷以下步驟
- 編寫文字,由於規則比較鬆散,可能存在多餘的前後空格,多餘的換行等情況
- 傳輸文字,傳輸的是 ASCII,即文字對應的編碼
- 讀取文字,理解文字,識別 ASCII 碼組合起來的單詞,再通過字串匹配的方式匹配意義
二進位制協議
- 這裡的二進位制,主要體現在兩個方面「二進位制幀封裝」及「頭部壓縮」
「二進位制幀封裝」即將資料打散,外包一層二進位制幀資料(用於標識當前幀的特性)
- 所以是這一分幀層進行了二進位制封裝,而不是 http 的內容二進位制,所以更合適的稱呼應該是「增加了二進位制分幀層」
- 這裡的二進位制幀資料,是用二進位制為顆粒度代表資料的含義,每個 0 和 1 代表特殊的含義
- 而文字協議,是通過 ASCII 對應的單詞來表達含義,
- 即代表資料的顆粒度不一樣了,原本是有若干個字母,現在是由若干個位元
「頭部壓縮」
- 通過靜態表和動態表的索引值來代表含義
- 相當於雙方建立的臨時暗號
http2 頭部壓縮,如何做到瀏覽器動態表與伺服器動態表的同步
- 【疑問】由於競速問題,在請求頭未註冊到伺服器之前接收了另一個請求。
- 【方案】http2 在分幀中有明確規則,請求頭與響應頭的分幀,中間不能插入其他分幀。從而保證完整的請求頭和響應頭是按順序傳輸的。故不會存在競速問題。
- stackoverflow 中對應的討論
- 在 HTTP2 中的 CONTINUATION 幀有相關的介紹,對應中文文件
- segmentfault 中關於 CONTINUATION 幀的介紹