HTTP/2 不再是文本,而是一个个二进制的帧,每帧 9 字节帧头 + 载荷,所有请求响应都在同一条连接的“流”里。这篇不借助任何 HTTP/2 库去收发请求,而是手工拼帧、手工读帧,看连接怎么开头、请求怎么压缩、多个流怎么共存、出错怎么回。
ALPN 协商结果:h2;TLS TLSv1.3
客户端发出:前言 b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'(24 字节)+ 一个 SETTINGS 帧(21 字节:9 字节帧头 + 2 个设置 × 6 字节)
SETTINGS 帧的字节:00 00 0c 04 00 00 00 00 00 00 03 00 00 00 64 00 04 00 00 ff ff
= 00 00 0c(长度 12)| 04(类型 SETTINGS)| 00(标志)| 00 00 00 00(流 0)| 00 03 00 00 00 64(MAX_CONCURRENT_STREAMS=100)| 00 04 00 00 ff ff(INITIAL_WINDOW_SIZE=65535)
服务器 → SETTINGS 流 0 长度 18 标志 - MAX_CONCURRENT_STREAMS=100, INITIAL_WINDOW_SIZE=65536, MAX_FRAME_SIZE=16777215
服务器 → WINDOW_UPDATE 流 0 长度 4 标志 - 窗口增量 2147418112
服务器 → SETTINGS 流 0 长度 0 标志 ACK
客户端对服务器的 SETTINGS 回了一个空的 SETTINGS ACK 帧(已发送):9 字节 = 00 00 00 | 04 | 01 | 00 00 00 0024 字节前言 + SETTINGS;服务器回 SETTINGS、WINDOW_UPDATE 与 ACK,客户端也要 ACK 它的 SETTINGS。
请求:HEADERS 帧 流 1,标志 END_HEADERS+END_STREAM(没有消息体),头部块 23 字节(HPACK),整个帧 32 字节;等价的 HTTP/1.1 文本请求 67 字节 头部块的字节:82 87 84 41 88 2f 91 d3 5d 05 5c 87 a7 7a 83 f0 f3 5f 53 83 f9 63 e7 服务器 → HEADERS 流 1 长度 160 标志 END_HEADERS 头部 :status: 200, date: Thu, 15 Oct 2026 10:34:08 GM, content-type: text/html; charset=utf-8, server: cloudflare, last-modified: Tue, 13 Oct 2026 22:05:48 GM, allow: GET, HEAD …共 11 个 服务器 → DATA 流 1 长度 577 标志 - 服务器 → DATA 流 1 长度 0 标志 END_STREAM 响应的头部块 160 字节(HPACK 压缩后),数据 577 字节;帧数 3;每个帧还要加 9 字节帧头
请求一个 HEADERS 帧,头部块用 HPACK 压缩;响应 HEADERS + DATA,结尾带 END_STREAM 标志。
收到的帧序列(类型@流):HEADERS@3 DATA@3 HEADERS@5 DATA@5 DATA@5 流 3 的帧 2 个,流 5 的帧 3 个;两条流各自以带 END_STREAM 标志的帧结束
客户端发起的流 ID 是奇数;帧通过流 ID 区分属于哪个请求。
5 个 PING 的往返:[151, 152, 153, 152, 152] ms;载荷原样返回:True;这就是应用层(HTTP/2)能看到的往返时间
8 字节载荷,原样返回,用于保活和测 RTT。
服务器 → SETTINGS 流 0 长度 18 标志 - MAX_CONCURRENT_STREAMS=100, INITIAL_WINDOW_SIZE=65536, MAX_FRAME_SIZE=16777215
服务器 → WINDOW_UPDATE 流 0 长度 4 标志 - 窗口增量 2147418112
服务器 → SETTINGS 流 0 长度 0 标志 ACK
服务器 → HEADERS 流 1 长度 57 标志 END_HEADERS 头部 :status: 400, server: cloudflare, date: Thu, 15 Oct 2026 10:34:11 GM, content-type: text/html, content-length: 155, cf-ray: -
服务器 → DATA 流 1 长度 155 标志 END_STREAM
DATA 内容:b'<html>\r\n<head><title>400 Bad Request</title></head>\r\n<body>\r\n<center><h1>400 Bad Request</h1></center>\r\n<hr><center>clou'缺少 :method 时 example.com 回 400 而不是 RST_STREAM / GOAWAY。