HTTP/2 多路复用原理

HTTP/2 如何通过二进制帧实现多路复用

问题

HTTP/2 中,多路复用的原理是什么?

解答

什么是多路复用

HTTP/1.1 的请求-响应模型是串行的:一个 HTTP 消息必须完成后,才能处理下一个。而 HTTP/2 允许多个消息在同一个连接上交织传输,这就是多路复用——在一个 TCP 连接上,多个 HTTP 消息同时工作。

HTTP/1.1 为什么不能多路复用

HTTP/1.1 是基于文本分割解析的协议。请求消息格式如下:

GET /index.html HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0

服务端需要不断读入字节,直到遇到换行符(\n\r\n)才能解析一行内容。这种解析方式存在两个问题:

一次只能处理一个消息:在遇到分隔符完成解析之前,无法停止或切换到其他消息。

内存分配不可预知:服务端不知道一行内容有多长,无法预先分配合适的缓冲区大小,既要保证解析效率,又要避免内存浪费。

HTTP/2 的帧结构

HTTP/2 是基于二进制帧的协议。帧是数据的基本单元,结构如下:

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                      ...
+---------------------------------------------------------------+

前 9 个字节对每个帧都是固定的:

  • Length (3 字节):帧负载的长度
  • Type (1 字节):帧类型(HTTP/2 定义了 10 种帧类型)
  • Flags (1 字节):帧标志位
  • Stream Identifier (4 字节):流 ID,标识帧所属的流

服务端只需解析前 9 个字节,就能准确知道整个帧的大小和类型,实现了”一切可预知,一切可控”。

多路复用的实现

HTTP/2 引入了”流”的概念:流是连接上独立的、双向的帧序列交换。每个帧的 Stream ID 标识它属于哪个流。

多路复用的工作方式:

  1. 客户端发起多个请求,每个请求分配一个唯一的 Stream ID
  2. 请求被拆分成多个帧,每个帧携带 Stream ID
  3. 这些帧可以交错发送,不需要等待前一个请求完成
  4. 服务端根据 Stream ID 将帧重新组装成完整的请求
  5. 响应同样以帧的形式交错返回

例如,一个 POST 请求可能被拆分为:

  • HEADERS 帧(Stream ID: 3)
  • DATA 帧(Stream ID: 3)

同时另一个 GET 请求的帧也在传输:

  • HEADERS 帧(Stream ID: 5)

这些帧在同一个 TCP 连接上交错传输,实现了真正的并发。

关键点

  • HTTP/2 基于二进制帧,每个帧前 9 字节包含长度、类型和流 ID,使解析可预知
  • HTTP/1.1 基于文本分割,必须串行解析,无法预知内存需求
  • 流(Stream)是帧的逻辑分组,通过 Stream ID 标识不同的请求/响应
  • 多个流的帧可以在同一连接上交错传输,实现真正的并发
  • 这种设计消除了 HTTP/1.1 的队头阻塞问题,显著提升了传输效率