HTTP/2 帧格式与流机制详解

一、帧、消息、流的关系

三者是 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),流程如下:

  1. 客户端创建流 3(奇数,客户端发起)承载 img.png 请求,流 5 承载 script.js 请求。
  2. 流 3 发送 HEADERS 帧(请求头)+DATA 帧(请求体,若有);流 5 同理。
  3. 服务器接收帧后,按流 ID 分组重组消息,处理后返回流 3 的 HEADERS 帧(响应头)+DATA 帧(响应体),流 5 同理。
  4. 客户端通过流 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 爆破) 无限制

关键说明

  1. 设置帧交互流程
    • 客户端 / 服务器可发送 SETTINGS 帧,携带配置参数。
    • 接收方必须回复 SETTINGS 帧(带 ACK 标志)确认,否则配置不生效。
    • 配置变更仅对后续流生效,不影响已打开的流。
  2. 帧类型扩展:Type 值 0xB~0xFF 为扩展类型,接收方若不支持可忽略,不影响其他帧处理。

0voice · GitHub

相关推荐
lowhot10 小时前
各种网络协议比较
网络·网络协议
运维有小邓@10 小时前
如何实现基于角色的访问控制?
运维·网络
EasyGBS10 小时前
EasyGBS打造变电站高效智能视频监控解决方案
网络·人工智能·音视频
东北小狐狸-Hellxz10 小时前
解决java客户端连接ssh失败问题
java·网络·ssh
可爱又迷人的反派角色“yang”10 小时前
k8s(一)
linux·运维·网络·云原生·容器·kubernetes
闲人不梦卿10 小时前
网络安全技术
网络·网络安全
可爱又迷人的反派角色“yang”10 小时前
CICD持续集成Ruo-Yi项目
linux·运维·网络·ci/cd·docker·容器
星环处相逢10 小时前
K8s 网络插件选型:Flannel vs Calico 深度对比
网络·容器·kubernetes
2501_9418227511 小时前
在开罗智能公共交通场景中构建实时调度与高并发乘客数据处理平台的工程设计实践经验分享
网络·安全
Zsr102311 小时前
K8s网络方案深度解析:Flannel vs Calico 怎么选?
网络·容器·kubernetes·flannel·calico