HTTP/2 核心:二進位分幀與多路複用
HTTP/2 是 HTTP 協定的一次重大革新。它從根本上改變了資料傳輸的方式,從文字協定變成二進位協定,從串行變成真正的並行。本篇將深入解析這些核心變革。
一、 從文字到二進位
1.1 HTTP/1.1 的文字協定
HTTP/1.1 使用純文字格式,人類可讀:
http
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
User-Agent: Mozilla/5.0...\r\n
Accept: text/html\r\n
\r\n問題:
| 問題 | 說明 |
|---|---|
| 解析複雜 | 需要逐字元掃描尋找分隔符 |
| 效率低 | 文字比二進位佔用更多空間 |
| 歧義可能 | 空格、換行處理可能不一致 |
1.2 HTTP/2 的二進位協定
HTTP/2 將所有資料封裝成幀(Frame):
HTTP/2 Frame:
┌─────────────────────────────────────┐
│ Length (24 bits) │
├─────────────────────────────────────┤
│ Type (8 bits) │ Flags (8 bits) │
├─────────────────────────────────────┤
│ R │ Stream Identifier (31 bits) │
├─────────────────────────────────────┤
│ Frame Payload (variable length) │
└─────────────────────────────────────┘優點:
| 優點 | 說明 |
|---|---|
| 解析簡單 | 固定格式,直接讀取位元組 |
| 更緊湊 | 二進位編碼更節省空間 |
| 無歧義 | 明確的欄位長度 |
| 可擴展 | 新增幀類型不破壞相容性 |
二、 幀(Frame)結構
2.1 幀標頭(9 bytes)
每個幀都有 9 位元組的標頭:
| 欄位 | 大小 | 說明 |
|---|---|---|
| Length | 24 bits | Payload 長度(最大 16MB) |
| Type | 8 bits | 幀類型 |
| Flags | 8 bits | 類型相關的標誌 |
| R | 1 bit | 保留位(必須為 0) |
| Stream ID | 31 bits | 串流識別碼 |
2.2 幀類型
HTTP/2 定義了 10 種幀類型:
| Type | 名稱 | 用途 |
|---|---|---|
| 0x00 | DATA | 傳輸請求/回應 Body |
| 0x01 | HEADERS | 傳輸標頭 |
| 0x02 | PRIORITY | 設定串流優先級 |
| 0x03 | RST_STREAM | 終止串流 |
| 0x04 | SETTINGS | 連線設定 |
| 0x05 | PUSH_PROMISE | 伺服器推送預告 |
| 0x06 | PING | 連線健康檢查 |
| 0x07 | GOAWAY | 優雅關閉連線 |
| 0x08 | WINDOW_UPDATE | 流量控制 |
| 0x09 | CONTINUATION | 延續 HEADERS |
2.3 常見幀類型詳解
DATA 幀
傳輸實際的請求或回應內容:
DATA Frame:
┌─────────────────────────────────────┐
│ Pad Length? (8 bits) │
├─────────────────────────────────────┤
│ Data (*) │
├─────────────────────────────────────┤
│ Padding (*) │
└─────────────────────────────────────┘
Flags:
- END_STREAM (0x1): 這是最後一個幀
- PADDED (0x8): 有填充資料HEADERS 幀
傳輸壓縮後的 HTTP 標頭:
HEADERS Frame:
┌─────────────────────────────────────┐
│ Pad Length? (8 bits) │
├─────────────────────────────────────┤
│ E │ Stream Dependency (31 bits) │
├─────────────────────────────────────┤
│ Weight (8 bits) │
├─────────────────────────────────────┤
│ Header Block Fragment (*) │
├─────────────────────────────────────┤
│ Padding (*) │
└─────────────────────────────────────┘
Flags:
- END_STREAM (0x1): 沒有 DATA 幀
- END_HEADERS (0x4): 標頭完整
- PADDED (0x8): 有填充
- PRIORITY (0x20): 有優先級資訊三、 串流(Stream)概念
3.1 什麼是串流?
串流是 HTTP/2 中的虛擬通道,每個請求/回應配對使用一個獨立的串流:
3.2 串流 ID 規則
| 類型 | ID 規則 | 範例 |
|---|---|---|
| 客戶端發起 | 奇數 | 1, 3, 5, 7... |
| 伺服器發起 | 偶數 | 2, 4, 6, 8... |
| 控制訊息 | 0 | SETTINGS, PING |
3.3 串流狀態機
3.4 串流生命週期範例
四、 多路複用(Multiplexing)
4.1 HTTP/1.1 的問題回顧
HTTP/1.1 即使有持久連線,一次也只能處理一個請求:
4.2 HTTP/2 的多路複用
HTTP/2 在單一連線上並行處理多個串流:
關鍵:回應可以亂序返回,快的先到!
4.3 幀交錯傳輸
實際上,不同串流的幀會交錯傳輸:
傳輸序列:
[HEADERS S1] [HEADERS S3] [DATA S3] [HEADERS S5]
[DATA S1 part1] [DATA S5] [DATA S1 part2] [DATA S1 END]藍色 = Stream 1, 綠色 = Stream 3, 橘色 = Stream 5
五、 解決隊頭阻塞
5.1 HTTP/1.1 的隊頭阻塞
5.2 HTTP/2 解決方案
快的請求不用等慢的!
5.3 但仍有 TCP 層阻塞
WARNING
HTTP/2 解決了應用層的隊頭阻塞,但傳輸層(TCP)的阻塞仍然存在。
如果一個 TCP 封包丟失,整個連線都要等待重傳。這是 HTTP/3 改用 QUIC 的原因。
六、 連線建立
6.1 升級方式
HTTP/2 有兩種建立方式:
1. TLS + ALPN(主流)
2. 明文升級(較少見)
http
GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64 設定>
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
<HTTP/2 連線開始>6.2 連線前言
客戶端必須發送魔術字串作為連線開始:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n接著發送 SETTINGS 幀。
七、 設定協商
7.1 SETTINGS 幀
連線開始時交換設定:
| 設定 | 預設值 | 說明 |
|---|---|---|
| HEADER_TABLE_SIZE | 4096 | HPACK 表大小 |
| ENABLE_PUSH | 1 | 允許 Server Push |
| MAX_CONCURRENT_STREAMS | 無限 | 最大並行串流數 |
| INITIAL_WINDOW_SIZE | 65535 | 初始流控視窗 |
| MAX_FRAME_SIZE | 16384 | 最大幀大小 |
| MAX_HEADER_LIST_SIZE | 無限 | 最大標頭大小 |
7.2 設定交換流程
八、 實際封包觀察
使用 Chrome DevTools 或 Wireshark 可以觀察 HTTP/2 通訊:
Chrome DevTools
Protocol 欄位顯示 h2
可看到所有請求使用同一個連線 IDcurl 測試
bash
# 觀察 HTTP/2 通訊
curl -v --http2 https://example.com
# 輸出包含:
# * Using HTTP2, server supports multiplexing
# * Connection state changed (HTTP/2 confirmed)
# * ALPN, negotiated h2總結
| 概念 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 格式 | 文字 | 二進位 |
| 傳輸單位 | 請求/回應 | 幀(Frame) |
| 並行處理 | 多連線 | 多路複用 |
| 隊頭阻塞 | 有 | 應用層解決 |
| 連線數 | 每域名 6 個 | 1 個 |
> **記住 HTTP/2 的三大核心**:
- 二進位分幀:高效解析
- 串流:請求/回應的虛擬通道
- 多路複用:單連線並行處理
進階挑戰
- 使用 Chrome DevTools 的 Network 面板,觀察一個 HTTP/2 網站的請求,確認所有請求使用同一個連線。
- 使用
nghttp命令列工具發送 HTTP/2 請求,觀察幀的交換過程。 - 思考:為什麼 HTTP/2 推薦只使用一個 TCP 連線,而不是像 HTTP/1.1 那樣開多個?