HTTP/2 Binary Framing(HTTP/2 二进制分帧机制)

Problem(解决什么问题)

HTTP/1.1 虽然引入了持续连接(Keep-Alive),但受限于纯文本解析与 FIFO 顺序响应的要求,存在严重局限:

  1. HTTP 应用层队头阻塞(HOL Blocking):同一个 TCP 连接上大对象传输会拖慢后续所有小对象的响应。
  2. 文本解析边界复杂:HTTP/1.1 需要解析换行符(\r\n)、空格、冒号以及消息体长度,多个中间件对边界的处理差异可能造成安全问题。
  3. 首部文本冗余:每次请求均重复传输大量相同的 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 ~ 2Length24 bits帧载荷 (Payload) 的字节长度
Byte 3Type8 bits帧类型编号(0~255;如 0x0 DATA, 0x1 HEADERS, 0x2 PRIORITY
Byte 4Flags8 bits控制标志位(如 END_STREAM = 0x1 标记流结束,PRIORITY = 0x20 开启优先级解析)
Byte 5 (Bit 0)R1 bit保留位 (Reserved, 置 0)
Byte 5 ~ 8Stream Identifier31 bits核心字段。唯一标识该帧属于哪一个 HTTP 请求/响应流(Stream ID)
Byte 9 +Frame Payload变长 (Variable)传输的实际二进制数据载荷;不同帧类型定义不同的负载格式

EStream DependencyWeight 不是通用帧头字段,而是旧优先级信号中的特定帧负载字段。例如 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)

  1. 彻底消除应用层 HOL 阻塞:大文件帧与小文件帧交错传输,小文件不再被挂起等待。
  2. CPU 解析极高效:CPU 直接读取固定 9 字节偏移量填充寄存器,位运算解析,无字符串扫描开销。
  3. 降低了部分边界歧义:二进制帧明确给出长度和类型,但不能据此声称消除了所有解析 Bug 或 HTTP 请求走私攻击。
  4. 支持双向流式与优先级调度:完美支撑现代高并发与双向 RPC 场景。

局限与开销 (Cons & Overhead)

  1. 字节结构税:若大报文被切分为 个帧,会产生 字节的帧头开销;实际收益还取决于帧大小、首部压缩和请求模式。
  2. 未消除 TCP 层队头阻塞:由于底层仍基于 TCP,若发生单个 TCP 报文丢包,TCP 协议栈会将整条连接挂起等待重传,导致所有并发流暂时阻塞(该局限促使 HTTP/3 改用基于 UDP 的 QUIC 协议)。