HTTP/3 传输层 QUIC 协议

HTTP/1.1 、HTTPS 、 HTTP/2 、 HTTP/3 协议结构

HTTP/3 是 HTTP 协议的第三个主要版本,于 2022 年正式标准化(RFC 9114)。它最大的变革是弃用 TCP,转而基于 QUIC 协议(Quick UDP Internet Connections,原为 Google 的 gQUIC),从根本上解决了 HTTP/2 中遗留的传输层队头阻塞问题,并进一步优化了连接建立与迁移。

QUIC 协议

QUIC 是一个基于 UDP 的、多路复用安全的传输层协议。它将 TLS 1.3 的加密直接融入,并内置了流量控制、丢包恢复等特性。HTTP/3 本质上就是"HTTP 语义 + QUIC 传输"。

数据包结构

QUIC 数据包分为 长头部 (Long Header)和 短头部(Short Header)。长头部用于连接建立(Initial、Handshake、Retry、Version Negotiation 等),短头部用于数据传输阶段。

长头部 (用于建立连接)

首字节最高位为 1:表示长头部。

长头部的数据包有:Initial、0-RTT、Handshake、Retry、Version Negotiation。

Initial 包
  • 用途:客户端发送的第一个包,用于启动连接。包含 TLS 1.3 的 ClientHello 以及 QUIC 传输参数。
  • 特点
    • 必须携带令牌(Token):首次连接时令牌为空;若服务端要求验证地址(如防止放大攻击),会通过 Retry 包下发令牌,客户端重发 Initial 时需携带。
    • 使用初始密钥加密(基于连接 ID 推导)。
    • 负载中包含 CRYPTO 帧 (承载 ClientHello)和可能的 ACK 帧
0-RTT 包
  • 用途 :客户端在连接恢复 时,无需等待握手完成即可发送应用数据。这些数据使用之前会话派生的 0-RTT 密钥加密。
  • 特点
    • 仅在客户端拥有服务器下发的 PSK (预共享密钥)或 Session Ticket 时使用。
    • 数据可能被重放攻击,因此服务端通常会限制 0-RTT 只用于幂等请求(如 GET)。
    • 0-RTT 包不能单独发送,必须与 Initial 包一同(或紧随其后)发送。
Handshake 包
  • 用途:用于携带 TLS 1.3 的 ServerHello、加密扩展、证书等握手消息。在客户端收到 Initial 包的响应后交换。
  • 特点
    • 使用 握手密钥(Handshake Key)加密,该密钥由初始密钥派生。
    • 通常由服务器首先发送,客户端随后也可能发送 Handshake 包(如 Finished 消息)。
    • 负载中包含 CRYPTO 帧 和可能的 ACK 帧
Retry 包
  • 用途 :服务器用于地址验证 ,要求客户端重新发送 Initial 包并携带服务器提供的令牌(Token)。这可以防御 放大攻击(攻击者伪造源 IP 发送小请求,诱使服务器发送大量响应)。
  • 特点
    • 不加密,负载中仅包含一个令牌(Token)和可选的连接 ID 重选信息。
    • 客户端收到 Retry 包后,必须丢弃之前的 Initial 包,使用新的令牌重新构造 Initial 包。
    • 仅由服务器发送,且只在连接建立的初始阶段使用。
Version Negotiation 包
  • 用途:当客户端发送的 QUIC 版本不被服务器支持时,服务器回复 Version Negotiation 包,列出自己支持的版本列表。
  • 特点
    • 无加密,仅包含版本列表(一个或多个 32 位版本号)。
    • 客户端收到后,从列表中选择一个支持的版本,或放弃连接。
    • 包中不包含连接 ID 字段(但包含客户端提供的连接 ID 用于路由)。

短头部 (用于数据传输)

主要特性

1、多路复用5️无阻塞,彻底解决队头阻塞

HTTP/2 的问题 :虽然请求级队头阻塞被解决,但 TCP 是可靠有序的,一个 TCP 分片丢失会阻塞该连接上所有后续流(直到重传完成),这叫 TCP 队头阻塞

HTTP/3 的方案 :QUIC 在单连接上提供独立的流,丢失某个流的数据包只影响该流,其他流可以继续交付。因为 UDP 本身无序,QUIC 在应用层实现了每流的可靠性和有序性。

2、0-RTT 快速握手

TCP + TLS 1.3 需要至少 1 个 RTT(往返时间)完成握手和加密协商。

QUIC 集成了 TLS 1.3,在首次连接时 1-RTT ,但如果之前连接过,可以立即发送数据(0-RTT) ,极大减少延迟(尤其适合移动设备频繁切换网络)。

3、连接无缝迁移

在 TCP 中,连接由(IP,端口)四元组唯一标识。当设备从 WiFi 切换到蜂窝网络时,IP 改变,所有 TCP 连接必须重新建立。

QUIC 使用 连接 ID(Connection ID)标识连接,不依赖 IP。因此即使 IP 或端口变化,连接仍可继续,无需重新握手。这对移动端体验提升巨大。

4、更好的丢包恢复

TCP 的丢包恢复依赖经典的快速重传和 SACK,而 QUIC 使用更精细的包头保护独立的包编号空间(每个流独立),并允许更灵活的拥塞控制算法(甚至可在用户空间实现)。

5、加密成为强制且默认

HTTP/2 允许明文(h2c),但主流实现强制 TLS。HTTP/3 要求必须使用 QUIC 的加密(相当于 TLS 1.3),没有明文版本。所有头部和载荷全程加密,包括原来明文传输的部分(如 HTTP/2 的伪头部)。

示例 利用 @currentspace/http3

js 复制代码
import { createSecureServer } from '@currentspace/http3';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 加载 SSL 证书和密钥
const key = readFileSync(join(__dirname, '../ssl/key.pem'));
const cert = readFileSync(join(__dirname, '../ssl/cert.pem'));

const PORT = 8444;

// 创建 HTTP/3 服务器
const server = createSecureServer({
  key,
  cert,
  allowHTTP1: false, // 禁用 HTTP/1.1
  // 禁用重试机制
  disableRetry: true,
});

// 监听会话事件
server.on('session', (session) => {
  console.log(`新会话: ${session.remoteAddress}:${session.remotePort} [ALPN: ${session.alpnProtocol}]`);
});

// 监听流事件
server.on('stream', (stream, headers) => {
  const path = headers[':path'] || '/';
  const method = headers[':method'] || 'GET';
  const protocol = headers[':scheme'] || 'https';

  console.log(`收到请求: ${method} ${path} [协议: ${protocol}]`);

  // 路由处理
  if (path === '/' || path === '/index.html') {
    // 返回测试页面
    const html = readFileSync(join(__dirname, '../public/index.html'));
    stream.respond({
      ':status': '200',
      'content-type': 'text/html; charset=utf-8',
      'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
    });
    stream.end(html);
  } else if (path === '/api/user') {
    stream.respond({
      ':status': '200',
      'content-type': 'application/json',
      'access-control-allow-origin': '*',
      'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
    });
    stream.end(
      JSON.stringify({
        code: 200,
        data: {
          id: 1,
          name: '张三',
          email: 'zhangsan@example.com',
          age: 28,
        },
        message: '获取用户信息成功',
        protocol: protocol,
      })
    );
  } else if (path === '/api/logo') {
    stream.respond({
      ':status': '200',
      'content-type': 'application/json',
      'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
    });
    stream.end(
      JSON.stringify({
        code: 200,
        data: {
          url: 'https://example.com/logo.png',
          width: 200,
          height: 100,
          alt: '公司 Logo',
        },
        message: '获取 Logo 信息成功',
        protocol: protocol,
      })
    );
  } else {
    stream.respond({
      ':status': '404',
      'content-type': 'application/json',
      'alt-svc': `h3=":${PORT}"; ma=86400; persist=1`,
    });
    stream.end(
      JSON.stringify({
        code: 404,
        message: 'Not Found',
      })
    );
  }
});

// 监听服务器启动事件
server.on('listening', () => {
  const addr = server.address();
  console.log('HTTP/3 + HTTP/2 服务已启动');
  console.log(`监听地址: https://localhost:${addr?.port ?? PORT}`);
  console.log('\n可用接口:');
  console.log('  - GET /api/user  获取用户信息');
  console.log('  - GET /api/logo  获取 Logo 信息');
  console.log('\n浏览器访问方式:');
  console.log(`  1. 首次访问: https://localhost:${PORT}/api/user (可能使用 HTTP/2)`);
  console.log(`  2. 刷新页面或再次访问,浏览器会自动升级到 HTTP/3`);
  console.log('\n提示:');
  console.log('  - 需要接受自签名证书警告');
  console.log('  - 清除浏览器缓存后重新访问');
  console.log('  - Chrome 可在 chrome://net-internals/#http3 查看HTTP/3连接状态');
});

// 监听错误事件
server.on('error', (err) => {
  console.error('服务器错误:', err);
});

// 启动服务
server.listen(PORT, '0.0.0.0');

为什么不能调用两次 stream.respond()?

在 HTTP/2 和 HTTP/3 协议中, 流(Stream)是单向的 ,一旦开始发送响应,就不能修改响应头部。

流生命周期 ?客户端请求 → 服务器接收 → stream.respond()stream.end() → 流关闭

检查浏览器是否支持 h3

chrome://flags/#enable-quic

  • HTTP/2 (TCP+TLS):证书不可信 → 浏览器弹出"不安全"警告 → 你点击"继续前往" → 可以访问
  • HTTP/3 (QUIC):证书不可信 → 直接静默失败,没有任何提示,也没有"继续前往"按钮

vite 代理

HTTP/3 基于 QUIC 协议(UDP) ,而 HTTP/1.1 和 HTTP/2 都基于 TCP 。当 http-proxy 尝试通过 TCP 连接到一个仅支持 HTTP/3 的服务器时,连接会失败,Vite 就会返回 502 Bad Gateway

curl 工具

检查当前是否已经安装过 curl, 如果已安装,需要先卸载。

bash 复制代码
# 查看路径
which curl
# /usr/bin/curl

安装支持 http3 的 curl

bash 复制代码
# 添加 Cloudflare 的 tap(提供优化版 curl)
brew tap cloudflare/cloudflare

# 安装 curl(此版本已集成 HTTP/3 支持)
brew install curl

部分提示

bash 复制代码
If you need to have curl first in your PATH, run:

  echo 'export PATH="/opt/homebrew/opt/curl/bin:$PATH"' >> ~/.zshrc

For compilers to find curl you may need to set:

  export LDFLAGS="-L/opt/homebrew/opt/curl/lib"
  export CPPFLAGS="-I/opt/homebrew/opt/curl/include"

For pkgconf to find curl you may need to set:

  export PKG_CONFIG_PATH="/opt/homebrew/opt/curl/lib/pkgconfig"

配置 PATH,让新 curl 优先于系统版本

bash 复制代码
echo 'export PATH="/opt/homebrew/opt/curl/bin:$PATH"' >> ~/.zshrc

source ~/.zshrc

再次查看路径

bash 复制代码
which curl
# /opt/homebrew/opt/curl/bin/curl

curl 测试

bash 复制代码
curl -k https://localhost:8444/api/user

新会话: 127.0.0.1:58984 [ALPN: h2]

收到请求: GET /api/user [协议: https]

bash 复制代码
curl --http3-only -k https://localhost:8444/api/user

新会话: 127.0.0.1:55838 [ALPN: h3]

收到请求: GET /api/user [协议: https]

检查服务器是否监听了 TCP 8444 端口

bash 复制代码
lsof -i :8444

杀掉 进程

ba 复制代码
lsof -ti :8444 | xargs kill -9

telnetnc 测试 TCP 连接,用 telnetnc 测试 TCP 连接

bash 复制代码
nc -zv localhost 8444

使用 -v 获取详细输出,curl试图连接的IP地址、端口、TLS握手过程。

bash 复制代码
curl -v https://localhost:8443/api/user

mkcert 证书

bash 复制代码
# 1. 安装 mkcert (macOS)
brew install mkcert
# 2. 在本地系统安装 CA(证书颁发机构)
mkcert -install
# 3. 为 localhost 生成证书文件,会在当前工作目录下生成证书文件
mkcert localhost 127.0.0.1 ::1
bash 复制代码
mkcert -uninstall

-uninstall 命令只移除了系统信任,并不会删除 mkcert 的本地 CA 文件

删除本地 CA 根证书和私钥文件

bash 复制代码
mkcert -CAROOT

支持浏览器用 h3 访问 后端项目

Chrome/Firefox 对 QUIC 连接的证书验证比 TCP TLS 更严格。

TCP TLS (h2) QUIC (h3)
自签名证书 ⚠️ 弹出警告,用户可跳过 静默失败,无法跳过
CT (证书透明度) 日志 不强制要求 Chrome 强制要求
mkcert 证书 ✅ 可用 没有 CT 日志,QUIC 连接被拒

mkcert 自签名证书没有 CT 日志,浏览器在后台尝试 QUIC 连接时会静默失败,所以永远回退到 h2。

强制 Chrome 允许 QUIC 使用自签名证书

关闭chrome浏览器,然后在 终端执行

bash 复制代码
# 关闭 Chrome,然后用以下命令启动
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --ignore-certificate-errors \
  --origin-to-force-quic-on=localhost:8444

--origin-to-force-quic-on 会强制 Chrome 对指定 origin 使用 QUIC,跳过证书验证。

访问 https://localhost:8443/api/user

新会话: ::1:57543 [ALPN: h3]

HTTP3 收到请求: GET /api/user [协议: https]

请求头、响应头

抓包

客户端发送 Initial 包,包含加密扩展(CRYPTO),开始 QUIC 握手。

QUIC IETF 是标准的 IETF QUIC 协议,HTTP/3 就运行在它之上。

Handshake , 服务端回复 Handshake 包,完成 TLS 1.3 握手。

Protected Payload (KPO),握手完成后,双方开始传输加密的应用数据(即 HTTP/3 请求与响应)。

最后

  1. mkcert工具
相关推荐
2401_873479404 小时前
如何用IP离线库阻断挖矿和僵尸网络?DNS层防护实战指南
网络·网络协议·tcp/ip·ip
light_in_hand5 小时前
HTTP 协议的基本格式和 fiddler 的用法
网络协议·http·fiddler
hai3152475436 小时前
九章编程法 · HTTP转发代理网关【终极完美版·矩阵步进交换】
人工智能·网络协议·线性代数·http·矩阵·极限编程
顾喵7 小时前
VME总线详解:原理、架构、时序、协议、迭代、调试与实战应用
linux·网络协议
qiuziqiqi9 小时前
webman的消费脚本进程中http请求的选择
网络·网络协议·http
努力成为AK大王9 小时前
TCP协议核心特点与首部详解
网络·网络协议·tcp/ip
light_in_hand9 小时前
HTTPS 加密流程总结
网络协议·http·https
之歆9 小时前
Node.js HTTP 模块深度解析与实战指南
网络协议·http·node.js
潜创微科技10 小时前
ITE IT920X 4K60 HDMI+USB over IP 远距离传输与视频墙单芯片方案
网络协议·tcp/ip·音视频