bigywater.tech
Java · 197

HTTP/2:帧、流和多路复用

HTTP/2 的帧、流和多路复用在真实连接上长什么样?这篇用 curl -v 看协商与流编号,用 nginx 日志数连接,用 Netty 的 HPACK 编码器量头部压缩(Java 的 HttpClient 怎么使用它见前面的篇目)。

1 vs 10
10 个并发请求:HTTP/2 与 HTTP/1.1 用的连接数
136 → 7 字节
HPACK 对相同请求头:第 1 次 → 第 2 次的编码大小
奇数流
客户端发起的流编号:1、3、5…

协商与伪头部

实测 · 本机执行输出
  ALPN: curl offers h2,http/1.1
  ALPN: server accepted h2
  using HTTP/2
  [HTTP/2] [1] OPENED stream for https://127.0.0.1:9602/fast
  [HTTP/2] [1] [:method: GET]
  [HTTP/2] [1] [:scheme: https]
  [HTTP/2] [1] [:authority: 127.0.0.1:9602]
  [HTTP/2] [1] [:path: /fast]
  [HTTP/2] [1] [user-agent: curl/8.7.1]
  [HTTP/2] [1] [accept: */*]
  < HTTP/2 200 

多路复用

实测 · 本机执行输出
  HTTP/2:总耗时 331 ms;nginx 日志里 10 个请求用了 1 个连接(协议 ['HTTP/2.0'])
  HTTP/1.1:总耗时 347 ms;nginx 日志里 10 个请求用了 10 个连接(协议 ['HTTP/1.1'])

流编号

实测 · 本机执行输出
  OPENED 的流编号:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19](都是奇数、递增;偶数留给服务端发起的流)

头部压缩

实测 · 本机执行输出
  第 1 次编码(请求头完全相同):HPACK 136 字节;同样的请求头写成 HTTP/1.1 文本是 190 字节
  第 2 次编码(请求头完全相同):HPACK 7 字节;同样的请求头写成 HTTP/1.1 文本是 190 字节
  第 3 次编码(请求头完全相同):HPACK 7 字节;同样的请求头写成 HTTP/1.1 文本是 190 字节
  第 4 次编码(只有 path 变了):HPACK 20 字节;同样的请求头写成 HTTP/1.1 文本是 191 字节

分步演示

HTTP/2
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。curl 8.7.1(nghttp2)、nginx(自己的配置、自签名证书、h2 与普通 TLS 两个端口)、JDK 21.0.12 + Netty 4.1.115.Final 的 HPACK 编码器
    • 没有验证:帧层(SETTINGS、WINDOW_UPDATE、PING、GOAWAY)的逐帧抓包、流量控制窗口、服务端推送、优先级、HTTP/2 在 TCP 丢包时的队头阻塞;HPACK 的数字只来自 Netty 编码器、没有抓真实的线路字节
    京ICP备2026067502号-1京公网安备11010502063487号