一、帧、消息、流的关系
三者是 HTTP/2 中数据传输的核心抽象,从微观到宏观依次为帧→消息→流,具体关系如下:
| 层级 | 定义 | 核心作用 | 关联规则 |
|---|---|---|---|
| 帧(Frame) | 最小传输单元,9 字节帧头 + 可变负载,二进制格式 | 承载具体数据 / 控制指令,是传输的原子单位 | 同一消息的帧共享同一流 ID,按流内顺序重组 |
| 消息(Message) | 完整的请求 / 响应(如 GET 请求、200 响应),由 1 个或多个帧组成 | 对应 HTTP/1 的请求 / 响应语义,封装头部 + 正文 | 1 个消息 = 1 个 HEADERS 帧 + 0 或多个 DATA 帧,由 END_STREAM/END_HEADERS 标志位标识完整 |
| 流(Stream) | 1 个 TCP 连接上的双向字节序列,用流 ID 唯一标识 | 实现多路复用,隔离并发请求,支持优先级与服务器推送 | 1 个流承载 1 个消息(请求 + 响应),流 ID 决定帧归属,不同流的帧可乱序传输 |
核心逻辑示例
客户端请求 2 个资源(img.png、script.js),流程如下:
- 客户端创建流 3(奇数,客户端发起)承载 img.png 请求,流 5 承载 script.js 请求。
- 流 3 发送 HEADERS 帧(请求头)+DATA 帧(请求体,若有);流 5 同理。
- 服务器接收帧后,按流 ID 分组重组消息,处理后返回流 3 的 HEADERS 帧(响应头)+DATA 帧(响应体),流 5 同理。
- 客户端通过流 ID 将帧重组为完整响应,实现单连接并行处理 2 个请求。
我用快递运输来类比上述概念:
HTTP/2 概念 快递场景类比 核心定位 帧(Frame) 单个快递包裹 最小运输单位,拆开才能看到里面的东西 消息(Message) 一套完整的快递(可能是 1 个或多个包裹) 有完整意义的内容,比如 "买的一套书" 流(Stream) 一条专属快递专线 区分不同 "订单" 的通道,互不干扰 TCP 连接 城市之间的主干道 所有专线都跑在这条主干道上 假设你打开浏览器,访问
https://example.com,页面需要加载 1 个 HTML 文件 + 2 张图片,HTTP/2 是这么处理的:1. 帧(Frame):拆成最小的 "包裹"
通俗理解:帧就是把要传输的数据,切成固定格式的小包裹(9 字节 "快递面单"+ 里面的货物)。不管是请求头、响应头还是图片数据,都得装成帧才能传输。
- "面单"(帧头):写着 属于哪个流、是什么类型的包裹、有没有装完。
- "货物"(负载):真正的内容(比如一部分 HTML 代码、一张图片的片段)。
例子细节 :服务器返回的 HTML 文件有 10KB,HTTP/2 默认把它切成 6 个 1.6KB 左右的帧 (因为默认最大帧负载是 16384 字节≈16KB),每个帧的 "面单" 上都写着 流 ID=1(表示属于同一个流)。
2. 消息(Message):凑成完整的 "订单"
通俗理解 :消息是有完整意义的一组帧,对应浏览器的一次请求或服务器的一次响应。
- 一个请求消息 = 1 个
HEADERS帧(装请求头:GET /index.html) + 0 个DATA帧(GET 请求没有请求体)。- 一个响应消息 = 1 个
HEADERS帧(装响应头:200 OK、Content-Type: text/html) + N 个DATA帧(装 HTML 内容)。- 怎么判断消息完整?看帧头的
END_HEADERS(头部装完了)和END_STREAM(所有数据装完了)标志位。例子细节 :服务器返回 HTML 的消息 = 1 个
HEADERS帧(标了END_HEADERS) + 6 个DATA帧(最后 1 个DATA帧标了END_STREAM)。这 7 个帧凑一起,才是完整的 HTML 响应。3. 流(Stream):开辟专属 "专线"
通俗理解 :流是一条虚拟的双向通道 ,跑在同一个 TCP 连接上。浏览器要同时要 3 个资源(HTML+2 张图),就会开 3 条流,每条流对应一个资源的 "请求 - 响应"。
- 每条流有唯一的
流ID(比如流 1、流 3、流 5,客户端开的流都是奇数)。- 不同流的帧可以混在一起传输 (比如流 1 的帧、流 3 的帧、流 5 的帧交替在 TCP 连接上跑),接收方根据帧头的
流ID分拣,再拼成各自的消息。- 这就是 HTTP/2 的多路复用------ 一条主干道(TCP 连接)上,同时跑多条专线(流),不用像 HTTP/1 那样排队。
例子细节:
资源 对应流 流的组成 HTML 文件 流 1(客户端发起,奇数) 浏览器发请求消息(流 1)→ 服务器回响应消息(流 1) 图片 1.png 流 3 浏览器发请求消息(流 3)→ 服务器回响应消息(流 3) 图片 2.png 流 5 浏览器发请求消息(流 5)→ 服务器回响应消息(流 5) 传输时,帧可能是这样的顺序:
流1的HEADERS帧 → 流3的HEADERS帧 → 流1的DATA帧 → 流5的HEADERS帧 → 流3的DATA帧 → ...浏览器收到后,按流 ID 分拣:流 1 的帧拼成 HTML,流 3 的拼成图片 1,流 5 的拼成图片 2,并行加载,互不耽误。
二、帧格式详解
HTTP/2 帧统一格式为9 字节帧头 + 可变长度负载,帧头字段如下(RFC 7540 定义):
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
帧头各字段说明
| 字段 | 长度 | 含义 | 约束 |
|---|---|---|---|
| Length | 24 位 | 负载长度(不含帧头 9 字节),默认最大 16384 字节(16KB),可通过 SETTINGS_MAX_FRAME_SIZE 调整 | 范围 0~2^24-1,超出则协议错误 |
| Type | 8 位 | 帧类型,决定负载格式与语义 | 0x0~0xA 为标准类型,其余为扩展 |
| Flags | 8 位 | 帧的控制标志(如 END_STREAM、END_HEADERS),不同帧类型有专属标志 | 未使用标志位必须置 0 |
| R | 1 位 | 保留位,必须置 0 | 非 0 将导致协议错误 |
| Stream ID | 31 位 | 帧所属流的唯一标识 | 0 为连接级控制帧,客户端流 ID 为奇数,服务器流 ID 为偶数,不可复用 |
三、STREAM 流 ID 的作用
流 ID 是 HTTP/2 多路复用的核心,31 位无符号整数,核心作用如下:
1. 实现多路复用
- 接收端通过流 ID 从乱序帧中分组,重组同一流的帧为有序消息,不同流的帧可并行处理,解决 HTTP/1 的队头阻塞。
- 示例:流 3 的 HEADERS 帧与流 5 的 DATA 帧可交错传输,接收端通过流 ID 分别组装,互不干扰。
2. 标识流归属与发起方
- 流 ID=0:仅用于连接级控制帧(如 SETTINGS、PING、GOAWAY),不承载业务数据。
- 客户端发起流:流 ID 为奇数(如 1、3、5...),升级场景中流 1 为响应流,客户端不可复用。
- 服务器发起流:流 ID 为偶数(如 2、4、6...),用于服务器推送(PUSH_PROMISE 帧)。
3. 流状态与生命周期管理
- 流 ID 严格递增,新流 ID 必须大于所有已打开 / 保留的流 ID,避免复用。
- 流 ID 关联流状态(空闲、打开、半关闭、关闭),如 RST_STREAM 帧通过流 ID 终止指定流。
4. 优先级与依赖管理
- 流 ID 用于标识优先级依赖(如流 3 依赖流 1),客户端通过 PRIORITY 帧指定,服务器据此优化资源调度。
四、帧类型及设置帧的子类型
HTTP/2 定义 10 种标准帧类型,按功能分为数据帧 和控制帧,具体如下:
标准帧类型(Type 字段值)
| 帧类型 | Type 值 | 功能 | 流 ID 约束 | 关键标志位 |
|---|---|---|---|---|
| DATA | 0x0 | 传输请求 / 响应体(正文) | 非 0 | PADDED(填充)、END_STREAM(流结束) |
| HEADERS | 0x1 | 传输 HTTP 头部,可携带优先级 | 非 0 | END_HEADERS(头部结束)、PRIORITY(含优先级) |
| PRIORITY | 0x2 | 设置流优先级与依赖 | 非 0 | 无 |
| RST_STREAM | 0x3 | 终止流(如错误),携带错误码 | 非 0 | 无 |
| SETTINGS | 0x4 | 协商连接 / 流配置(核心控制帧) | 0(连接级) | ACK(确认配置) |
| PUSH_PROMISE | 0x5 | 服务器推送资源,预告请求 | 非 0(偶数,服务器发起) | END_HEADERS |
| PING | 0x6 | 心跳检测,计算 RTT | 0 | ACK |
| GOAWAY | 0x7 | 通知对方关闭连接,携带最后流 ID | 0 | 无 |
| WINDOW_UPDATE | 0x8 | 更新流 / 连接的流量控制窗口 | 0(连接级)或非 0(流级) | 无 |
| CONTINUATION | 0x9 | 续传 HEADERS/PUSH_PROMISE 的头部(头部过长时) | 与前帧流 ID 一致 | END_HEADERS |
设置帧(SETTINGS)的子类型(配置参数)
SETTINGS 帧(Type=0x4)用于协商连接级配置,负载为多个键值对,子类型(配置项)如下:
| 配置参数 | 标识 | 作用 | 默认值 |
|---|---|---|---|
| SETTINGS_HEADER_TABLE_SIZE | 0x1 | 调整 HPACK 头部压缩表大小 | 4096 字节 |
| SETTINGS_ENABLE_PUSH | 0x2 | 启用 / 禁用服务器推送 | 1(启用) |
| SETTINGS_MAX_CONCURRENT_STREAMS | 0x3 | 最大并发流数 | 无限制 |
| SETTINGS_INITIAL_WINDOW_SIZE | 0x4 | 流初始流量控制窗口大小 | 65535 字节 |
| SETTINGS_MAX_FRAME_SIZE | 0x5 | 最大帧负载长度 | 16384 字节(16KB) |
| SETTINGS_MAX_HEADER_LIST_SIZE | 0x6 | 最大头部列表大小(防 HPACK 爆破) | 无限制 |
关键说明
- 设置帧交互流程 :
- 客户端 / 服务器可发送 SETTINGS 帧,携带配置参数。
- 接收方必须回复 SETTINGS 帧(带 ACK 标志)确认,否则配置不生效。
- 配置变更仅对后续流生效,不影响已打开的流。
- 帧类型扩展:Type 值 0xB~0xFF 为扩展类型,接收方若不支持可忽略,不影响其他帧处理。