215 lines
7.4 KiB
Markdown
215 lines
7.4 KiB
Markdown
|
|
---
|
|||
|
|
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<br/>问题重重"] -->|"串行请求"| B["每个请求等响应"]
|
|||
|
|
B --> C["慢 ❌"]
|
|||
|
|
|
|||
|
|
D["HTTP/2<br/>二进制分帧"] -->|"多路复用"| 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)
|
|||
|
|
|
|||
|
|
← 客户端在还没解析到 <link> 之前就已经收到了 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 `<link rel="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
|