1. QUIC 协议概述:从诞生到连接建立

大家好,我是这次课程的主讲。咱们直接进入正题——QUIC 协议。

先说说我个人的感受。做了十几年网络故障排查,TCP 那套东西我闭着眼都能背出来。但最近几年,我越来越频繁地在抓包文件里看到 UDP 端口 443 的流量,而且连接建立快得离谱。嗯,这就是 QUIC。

QUIC 全称是 Quick UDP Internet Connections,发音跟“quick”一样。Google 在 2012 年左右开始搞的,2015 年提交给 IETF 标准化,2021 年正式发布 RFC 9000。说白了,它就是冲着替代 TCP 来的。

1.1 QUIC 的诞生背景

为什么要有 QUIC?我举个例子你就明白了。

2010 年左右,Google 发现一个严重问题:用户在移动网络下打开搜索结果页,平均要等 2-3 秒。为什么?TCP 三次握手 + TLS 握手,光握手就两轮 RTT。再加上 HTTP 队头阻塞,慢得让人抓狂。

我记得当时有个项目,用户反馈“搜索结果加载太慢”。我们排查了半天,发现 40% 的延迟都花在连接建立上。TCP 的短板太明显了:

  • 握手延迟高:TCP 三次握手至少 1 个 RTT,TLS 1.2 还要 2 个 RTT,加起来 3 个 RTT 才能发数据
  • 队头阻塞严重:TCP 是严格有序的,一个包丢了,后面所有包都得等着
  • 迁移困难:TCP 连接靠五元组(源 IP、源端口、目的 IP、目的端口、协议)标识,换个网络就得断连
  • 内核僵化:TCP 实现在操作系统内核里,想升级?等几年吧

Google 的想法很简单:既然 TCP 改不动,那就基于 UDP 重新造一个。QUIC 就这么诞生了。

核心痛点总结:TCP 在移动互联网时代已经力不从心,QUIC 的目标就是解决握手延迟、队头阻塞、连接迁移这三大顽疾。

1.2 QUIC 相比 TCP 的优势

咱们直接上对比表,这样最清楚:

特性 TCP + TLS 1.3 QUIC (RFC 9000)
传输层协议 TCP(内核态) UDP(用户态)
握手延迟 1-RTT(TLS 1.3 下) 0-RTT(首次 1-RTT)
队头阻塞 存在(TCP 级别) 无(多流独立)
连接迁移 不支持(五元组绑定) 支持(Connection ID)
升级方式 内核升级,周期长 用户态更新,灵活
加密 可选(TLS 层) 强制(内置加密)

你想想看,QUIC 把加密从可选项变成了必选项。这不是拍脑袋决定的——我在实际排查中发现,很多 TCP 连接被中间设备篡改数据,就是因为没加密。QUIC 强制加密,直接堵死了这个漏洞。

还有一个细节:QUIC 的多路复用。HTTP/2 虽然也支持多路复用,但底层还是 TCP。一个 TCP 连接上跑多个 HTTP/2 流,只要一个流丢包,所有流都得等。QUIC 不一样,每个流是独立的,一个流丢包不影响其他流。这个特性在弱网环境下特别明显。

实战经验:我曾经在 30% 丢包率的模拟环境下测试,QUIC 的页面加载时间比 TCP+TLS 快了 3 倍以上。核心原因就是队头阻塞被彻底消除了。

1.3 QUIC 协议栈架构

QUIC 的协议栈长什么样?我画了一张图,你一看就明白:

QUIC 协议栈架构 应用层(HTTP/3, DNS-over-QUIC, gRPC 等) 应用协议直接运行在 QUIC 之上,不再依赖 TCP QUIC 传输层 连接管理 握手 / 迁移 / 关闭 多路复用 独立流 / 优先级 可靠传输 重传 / ACK / FEC 拥塞控制 NewReno / BBR / CUBIC 加密层 TLS 1.3 集成 连接迁移 Connection ID UDP 层 无连接、无状态、无重传 IP 层 QUIC 在用户态实现传输层功能,运行在 UDP 之上,避免了内核升级的麻烦

这张图我建议你保存下来。QUIC 的架构核心就一句话:在用户态实现传输层。这意味着什么?意味着你可以随时更新 QUIC 的实现,不用等操作系统升级。

我遇到过很多团队,因为内核版本太老,TCP 的新特性(比如 BBR 拥塞控制)根本用不了。QUIC 完全没这个问题——你只需要更新一下用户态的库就行。

1.4 QUIC 连接建立流程

这是咱们课程的重点。QUIC 的连接建立,说白了就是一次握手搞定一切。

先看标准流程(首次连接):

  1. 客户端发送 Initial 包:包含客户端随机数、支持的加密套件、初始源连接 ID
  2. 服务端回复 Initial + Handshake 包:包含服务端随机数、证书、加密参数、目的连接 ID
  3. 客户端发送 Handshake 包:完成密钥协商,生成应用数据密钥
  4. 双方开始发送应用数据:此时已经加密

整个过程只需要 1-RTT。对比 TCP + TLS 1.3 的 1-RTT,看起来一样?别急,QUIC 还有大招。

0-RTT 握手:如果客户端之前连接过同一服务端,可以缓存服务端的配置。下次连接时,客户端直接发送 0-RTT 数据包,里面就带着应用数据。服务端收到后,直接用缓存的密钥解密。整个过程 0 个 RTT 就能发数据。

我举个例子你就懂了。你打开一个网站,第一次加载需要 1-RTT。但当你刷新页面时,QUIC 可以直接在第一个包里带上请求数据。服务端收到后立刻处理,响应也立刻回来。这就是 0-RTT 的魅力。

但这里有个坑。我曾经排查过一个线上问题:用户频繁刷新页面,结果服务端收到大量重复的 0-RTT 请求。为什么?因为 0-RTT 存在重放攻击的风险。QUIC 的解决方案是:服务端必须检测 0-RTT 数据是否重复,如果发现重复,直接丢弃。

避坑指南:0-RTT 虽然快,但不适合幂等性要求高的操作(比如支付、下单)。我建议只在 GET 请求或幂等操作中使用 0-RTT。非幂等操作,老老实实用 1-RTT 握手。

咱们再细看一下连接建立的包交互过程。我用一个简化的流程图来说明:

QUIC 首次连接流程(1-RTT) 客户端 服务端 ① Initial 包(客户端随机数 + 连接 ID) ② Initial + Handshake 包(服务端证书 + 参数) ③ Handshake 包(完成密钥协商) ④ 应用数据(已加密) 关键点:握手和加密在同一个 RTT 内完成 0-RTT 场景下,步骤 ④ 可以直接在步骤 ① 中发送 服务端用缓存的密钥直接解密 0-RTT 数据

看到没?QUIC 把 TCP 的三次握手和 TLS 的加密握手合并了。这就是它快的根本原因。

最后说一个我踩过的坑。有一次排查 QUIC 连接超时,发现客户端一直在发 Initial 包,但服务端就是不回。抓包一看,服务端防火墙把 UDP 443 端口给封了。嗯,QUIC 虽然好,但前提是网络设备得放行 UDP 流量。很多企业网络只开放 TCP 443,QUIC 包直接被丢了。

实战建议:部署 QUIC 之前,先确认中间网络设备(防火墙、负载均衡器、DPI 设备)是否支持 UDP 443 的透传。如果不支持,QUIC 会优雅降级到 TCP,但 0-RTT 的优势就没了。