--- tags: [计算机网络, HTTP/2, HPACK, Multiplexing, Server Push] create time: 2026-05-18 02:40 --- # HTTP/2 多路复用 ## 概述 HTTP/2 (RFC 7540) 是 HTTP 协议自 1999 年 HTTP/1.1 以来的第一次重大修订。核心改进:**二进制分帧层**,让一个 TCP 连接上能并发多个请求,彻底消除 Head-of-Line blocking(应用层层面)。 ```mermaid flowchart TD A["HTTP/1.1
问题重重"] -->|"串行请求"| B["每个请求等响应"] B --> C["慢 ❌"] D["HTTP/2
二进制分帧"] -->|"多路复用"| E["并发请求/响应"] E --> F["快 ✅"] style A fill:#FF6B6B,color:#fff style D fill:#98FB98,color:#000 ``` ## HTTP/2 的三层层级 ``` ┌─────────────────────────────────────┐ │ Application Layer │ │ HTTP Frames / Streams │ ← 你操作的对象 ├─────────────────────────────────────┤ │ Frame Layer │ ← HPACK + Flow Control │ Binary Framing + Headers │ ├─────────────────────────────────────┤ │ Transport Layer │ ← TCP (or QUIC for H3) │ Connection Management │ └─────────────────────────────────────┘ ``` ## 二进制帧类型(Frame Types) | Frame | 说明 | |-------|------| | **HEADERS** | 请求/响应头部 | | **DATA** | 实际数据载荷 | | RST_STREAM | 异常关闭流 | | SETTINGS | 通信参数协商 | | PUSH_PROMISE | 服务器推送 | | PING | 延迟测量 | | GOAWAY | 优雅关闭连接 | | WINDOW_UPDATE | 流量控制 | | CONTINUATION | HEADERS 太长时的延续帧 | ### HEADERS 帧结构 ``` +---------------+ |Pad Length? |(0-7 bits) |Padding (8-bit)| |E | |+---------------+----------------------------------------------+ | |Header Block Fragment (*) | |+-----------------------------------------------------+ |... | +-----------------------------------------------------+ ``` ## Stream — 多路复用的核心 HTTP/2 将每个请求/响应绑定到一个唯一的 **Stream ID**: ``` TCP Connection: client:45678 ↔ server:443 Stream 1: GET /index.html → 200 OK Stream 3: GET /style.css → 200 OK Stream 5: POST /api/login → 200 OK Stream 7: GET /app.js → 200 OK Client Server │ │ │ ───HEADERS(stream=1)──→ │ GET /index.html │ ───DATA(stream=1, 5KB)──→ │ │ │ │ ←──HEADERS(stream=1, 200 OK)── │ │ ←──DATA(stream=1, 15KB)── │ │ │ │ ───HEADERS(stream=3)──→ │ GET /style.css │ ←──HEADERS(stream=3, 200 OK)── │ │ ←──DATA(stream=3, 3KB)── │ │ │ │ ───HEADERS(stream=5)──→ │ POST /api/login │ ───DATA(stream=5, 50B)──→ │ body: {"user":"alice"} │ ←──HEADERS(stream=5, 200 OK)── │ │ ←──DATA(stream=5, 80B)── │ body: {"token":"abc"} ``` ### Stream 状态机 ``` idle → reserved(local) ———→ half closed(local) —→ closed ↑ ↓ ↑ │ half closed(remote) │ │ ↓ │ └──── open ←───────── half closed(remote) ◄─────┘ ↑ ↑ │ half closed(local) │ ↓ send HEADERS closed recv HEADERS ``` ### 优先级分组(Stream Dependency) ``` Stream 1 (weight=16) [root] ├── Stream 3 (weight=8): /index.html │ ├── Stream 7 (weight=4): style.css │ └── Stream 9 (weight=2): logo.png ├── Stream 5 (weight=8): API data └── Stream 11 (weight=1): tracking pixel ``` ## HPACK — Header 压缩 HTTP/1.1 每次请求都重复发送 `User-Agent`、`Cookie`、`Accept` 等头部,浪费带宽。HPACK 用两种技术解决: | 技术 | 说明 | |------|------| | **Header Table** (动态表) | 服务端缓存常用头,后续请求用索引代替 | | **Huffman Coding** | 对字符串值做霍夫曼编码压缩 | ### HPACK 编码示例 原始 HTTP/1.1 请求头部(重复发送): ``` GET /page HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 ... Accept: text/html Cookie: session=abc123 Accept-Encoding: gzip ``` 重复 10 次后,HTTP/2 只发一次完整头部,后续使用动态表索引: ``` 索引 1: Host: example.com (查表! 不重传) 索引 4: User-Agent: ... (查表!) 索引 5: Accept: text/html (查表!) 索引 6: Cookie: session=abc123 (查表!) 索引 7: Accept-Encoding: gzip (查表!) ``` **节省效果:** header 体积减少 70%~90%。 ## Server Push 服务器可以主动向客户端推送资源,无需客户端请求: ``` Client: GET /index.html Server: HEADERS(stream=2, 200 OK) — HTML content Server: PUSH_PROMISE(stream=2, promised_stream=4) → style.css Server: HEADERS(stream=4, 200 OK) Server: DATA(stream=4, CSS content) ← 客户端在还没解析到 之前就已经收到了 CSS! ``` ```bash # Go 中启用 HTTP/2 Server Push pusher, ok := w.(http.Pusher) if ok { pusher.Push("/static/style.css", nil) } ``` > [!warning] Push 的实际争议 > Push 被广泛认为弊大于利:浏览器可能已经缓存了资源(导致冗余传输),且无法像 Client-initiated 那样利用浏览器已有的缓存决策。现代实践中更多使用 Preload `` 替代 Push。 ## HTTP/2 vs HTTP/1.1 对比 | 特性 | HTTP/1.1 | HTTP/2 | |------|---------|--------| | 格式 | 文本 | **二进制** | | 并发 | 需要 N 个连接 | **单连接多路复用** | | Header | 明文全量发送 | **HPACK 压缩** | | 服务端推送 | ❌ | ✅ (可选) | | 头部压缩 | ❌ (需 HPACK extension) | ✅ 原生支持 | | 头部顺序 | 按发送顺序 | 可重新排序 | | HoL Blocking | 严重 (TCP 层) | **消除** (Stream 独立) | | TLS 要求 | 不需要 | **推荐** (nginx/apache 默认 h2c) | ## h2c — HTTP/2 Clear Text (未加密) HTTP/2 可以通过 ALPN 升级(TLS 下的 h2)或 HTTP Upgrade 机制(纯 TCP 的 h2c): ```bash # curl 测试 h2c curl --http2 -H "Upgrade-H2C: true" http://localhost:8080/api # Go net/http 中 h2c 支持需 import golang.org/x/net/http2 import "golang.org/x/net/http2" import "golang.org/x/net/http2/h2c" ``` > [!tip] 生产环境始终用 HTTPS + h2 > 浏览器仅支持 TLS 下的 HTTP/2 (ALPN = "h2")。纯 TCP 的 h2c 主要用于微服务间通信。 ## 关联笔记 - [[hhs/NETWORK/HTTP/1.1完全指南]] — HTTP/2 的基础 - [[hhs/NETWORK/HTTP/3与QUIC]] — HTTP/2 的 UDP 替代品 - [[hhs/NETWORK/HTTPS与TLS握手]] — HTTP/2 通常配合 HTTPS