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 的架构核心就一句话:在用户态实现传输层。这意味着什么?意味着你可以随时更新 QUIC 的实现,不用等操作系统升级。
我遇到过很多团队,因为内核版本太老,TCP 的新特性(比如 BBR 拥塞控制)根本用不了。QUIC 完全没这个问题——你只需要更新一下用户态的库就行。
1.4 QUIC 连接建立流程
这是咱们课程的重点。QUIC 的连接建立,说白了就是一次握手搞定一切。
先看标准流程(首次连接):
- 客户端发送 Initial 包:包含客户端随机数、支持的加密套件、初始源连接 ID
- 服务端回复 Initial + Handshake 包:包含服务端随机数、证书、加密参数、目的连接 ID
- 客户端发送 Handshake 包:完成密钥协商,生成应用数据密钥
- 双方开始发送应用数据:此时已经加密
整个过程只需要 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 把 TCP 的三次握手和 TLS 的加密握手合并了。这就是它快的根本原因。
最后说一个我踩过的坑。有一次排查 QUIC 连接超时,发现客户端一直在发 Initial 包,但服务端就是不回。抓包一看,服务端防火墙把 UDP 443 端口给封了。嗯,QUIC 虽然好,但前提是网络设备得放行 UDP 流量。很多企业网络只开放 TCP 443,QUIC 包直接被丢了。
实战建议:部署 QUIC 之前,先确认中间网络设备(防火墙、负载均衡器、DPI 设备)是否支持 UDP 443 的透传。如果不支持,QUIC 会优雅降级到 TCP,但 0-RTT 的优势就没了。