HTTP/2 Binary Framing(HTTP/2 二进制分帧机制)
Problem(解决什么问题)
HTTP/1.1 虽然引入了持续连接(Keep-Alive),但受限于纯文本解析与 FIFO 顺序响应的要求,存在严重局限:
- HTTP 应用层队头阻塞(HOL Blocking):同一个 TCP 连接上大对象传输会拖慢后续所有小对象的响应。
- 文本解析边界复杂:HTTP/1.1 需要解析换行符(
\r\n)、空格、冒号以及消息体长度,多个中间件对边界的处理差异可能造成安全问题。 - 首部文本冗余:每次请求均重复传输大量相同的 ASCII 首部字符串。
Basic Idea(核心思想)
在 HTTP 应用层逻辑与底层 TCP 传输层之间引入 二进制分帧层(Binary Framing Layer):
- 解耦逻辑报文与物理帧:逻辑上保留 HTTP 报文(Message)概念(请求/响应),物理传输上将其切碎为二进制帧(Frame)。
- 请求与响应物理同构:客户端与服务端统一发送和接收标准的 9 字节帧头二进制帧。
- 交错传输(多路复用):将 HTTP 报文切分为带有
Stream ID的独立二进制帧,在单条 TCP 连接上交错传输;不同流可以并行,各流内部仍保持顺序。
Working Process(工作流程与帧结构)
1. 二进制帧物理结构 (Frame Structure)
HTTP/2 将所有传输数据统一封装为包含 9 字节固定帧头 的二进制帧。
在 Obsidian 中,使用 Mermaid packet-beta 原生数据包图呈现物理比特切块:
packet-beta 0-23: "Length (24 bits) [载荷长度]" 24-31: "Type (8 bits) [帧类型]" 32-39: "Flags (8 bits) [标志位]" 40: "R" 41-71: "Stream Identifier (31 bits) [流标识符]" 72-103: "Frame Payload (变长载荷示意)"
帧字段物理结构对照表
| 字节下标 (Byte Index) | 字段名称 (Field) | 比特长度 (Bit Width) | 物理作用与核心细节 |
|---|---|---|---|
| Byte 0 ~ 2 | Length | 24 bits | 帧载荷 (Payload) 的字节长度 |
| Byte 3 | Type | 8 bits | 帧类型编号(0~255;如 0x0 DATA, 0x1 HEADERS, 0x2 PRIORITY) |
| Byte 4 | Flags | 8 bits | 控制标志位(如 END_STREAM = 0x1 标记流结束,PRIORITY = 0x20 开启优先级解析) |
| Byte 5 (Bit 0) | R | 1 bit | 保留位 (Reserved, 置 0) |
| Byte 5 ~ 8 | Stream Identifier | 31 bits | 核心字段。唯一标识该帧属于哪一个 HTTP 请求/响应流(Stream ID) |
| Byte 9 + | Frame Payload | 变长 (Variable) | 传输的实际二进制数据载荷;不同帧类型定义不同的负载格式 |
E、Stream Dependency 和 Weight 不是通用帧头字段,而是旧优先级信号中的特定帧负载字段。例如 PRIORITY 帧的负载包含这 5 个字节;不能把它们画成每个 HTTP/2 帧都拥有的字段。
2. CPU 极速解析算法(内存偏移量计算)
由于物理帧头固定为 9 字节,解析器可以先读取长度字段,再按帧边界读取负载:
内存字节流:
[ 9 字节帧头 ] [ Length 字节 Payload ] [ 9 字节帧头 ] [ Length 字节 Payload ] ...
^ ^ ^
当前偏移量 + 9 + 9 + Length (下一帧入口)3. 关键控制特性
① 流式传输(Streaming)与 END_STREAM 标志位
- 在
DATA帧传输中,只要Flags中的END_STREAM (0x1)为 0,接收端就保持监听并持续接收后续帧(支撑 ChatGPT 式逐字输出、视频流或双向 gRPC 流式传输)。 - 当最后一帧到达时,打上
END_STREAM = 0x1,正式宣告该 Stream 传输完毕。
② 流优先级排序 (Stream Prioritization)
- 客户端可在帧载荷中显式指定
Stream Dependency(31 bit 流依赖)、E独占标记位 与Weight(8 bit 权重,1~256)。 - 服务端据此构建依赖树,在带宽有限时优先向关键资源(如 CSS/JS 脚本)倾斜分配带宽。
③ 服务器主动推送 (Server Push) 与 103 Early Hints
- 设计初想:服务端收到
index.html请求时,预测客户端需要common.css,主动通过PUSH_PROMISE帧预先推送至客户端。 - 现实局限与废弃:由于服务端无法感知客户端本地浏览器缓存,导致大量盲目推送浪费流量,Chrome 于 2022 年 (Chrome 106) 正式废弃 Server Push。
- 相关机制:
<link rel="preload">与103 Early Hints可以让客户端提前获知资源提示;103是信息性 HTTP 响应,不是 HTTP/2 Server Push 的帧级替代品。
Trade-off(设计权衡)
优势 (Pros)
- 彻底消除应用层 HOL 阻塞:大文件帧与小文件帧交错传输,小文件不再被挂起等待。
- CPU 解析极高效:CPU 直接读取固定 9 字节偏移量填充寄存器,位运算解析,无字符串扫描开销。
- 降低了部分边界歧义:二进制帧明确给出长度和类型,但不能据此声称消除了所有解析 Bug 或 HTTP 请求走私攻击。
- 支持双向流式与优先级调度:完美支撑现代高并发与双向 RPC 场景。
局限与开销 (Cons & Overhead)
- 字节结构税:若大报文被切分为 个帧,会产生 字节的帧头开销;实际收益还取决于帧大小、首部压缩和请求模式。
- 未消除 TCP 层队头阻塞:由于底层仍基于 TCP,若发生单个 TCP 报文丢包,TCP 协议栈会将整条连接挂起等待重传,导致所有并发流暂时阻塞(该局限促使 HTTP/3 改用基于 UDP 的 QUIC 协议)。