QUIC (Quick UDP Internet Connections) 是由 Google 设计、后经 IETF 标准化的下一代传输层协议。(因此有gQUIC和IETF QUIC两个版本)它是 HTTP/3 的底层核心,旨在替代传统的 “TCP + TLS” 组合,让互联网传输更快、更安全、更稳定。
Quic相关电子书网站:简体中文 | HTTP/3 explained QUIC rfc文档:RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport
QUIC基础概念
1. Packet(数据包):物理运输单位
Packet 是 QUIC 在网络上传输的最小单位,它被封装在 UDP 报文的 Payload(净荷)中。
- 它的职责:
- 封装与加密:Packet 拥有报文头(Header),负责标识连接 ID(CID)、版本号和包序号(Packet Number)。它是 QUIC 加密的边界,整个 Packet(除极少数头部字段)都是加密的。
- 确认与重传:QUIC 的丢包检测和拥塞控制是基于 Packet 的。每发一个 Packet,它的序列号(Packet Number)就加 1。如果对方没收到,重传的是 Packet 里的内容,但会装进一个新的 Packet(新序号)里。
- 层级关系:一个 UDP 包里可以塞一个或多个 QUIC Packet。
2. Stream(流):逻辑业务单位
Stream 是应用层看到的数据通道。在 QUIC 连接中,你可以同时开启成百上千个 Stream。
- 它的职责:
- 多路复用:这是 QUIC 解决队头阻塞的关键。Stream 之间是相互独立的。Stream 1 丢了包,Stream 2 照样走,不会因为木材没到就耽误钢铁卸货。
- 有序性:虽然 Packet 到达可能乱序,但同一个 Stream 内部的数据必须是有序的。QUIC 会根据 Offset(偏移量)在接收端把数据拼好交给应用层。
- 流量控制:Stream 级别有自己的窗口限制,防止某个 Stream 消耗掉所有内存。
- 层级关系:Stream 是一个虚拟概念,它的数据会被拆分成许多块,塞进不同的 Frame 里。
3. Frame(帧):功能指令单位
Frame 是 Packet 内部的最小功能单元。一个 Packet 的 Payload(有效载荷)部分是由一个或多个 Frame 组成的。
- 它的职责:
- 具体的动作:有的 Frame 负责运数据(STREAM Frame),有的负责签收确认(ACK Frame),有的负责报错(CONNECTION_CLOSE Frame)。
- 混合装载:一个 Packet(车厢)里可以同时装:
- 来自 Stream 1 的一段数据(STREAM Frame)
- 来自 Stream 4 的一段数据(STREAM Frame)
- 对之前收到的包的确认信息(ACK Frame)
- 层级关系:Frame 存在于 Packet 之中。
一个典型的交互场景:
- 应用层:我想发一张图片(开启 Stream 5)。
- QUIC 协议层:把图片数据切成 3 块,每块打成一个 STREAM Frame。
- 打包:把第一个 Frame 塞进 Packet 1,把第二个 Frame 塞进 Packet 2…(同时可能往 Packet 1 里顺便塞一个 ACK Frame 确认对方的消息)。
- 传输:将 Packet 封装进 UDP 发走。
- 接收:对方收到 UDP 包,解密 Packet,拆出 Frame。如果是 STREAM Frame,就根据里面的 Stream ID 5 和 Offset 还原出图片。
为什么要发明 QUIC?(解决 TCP 的痛点)
在 QUIC 出现之前,网页加载主要靠 TCP + TLS。但随着移动互联网的发展,这套组合显露出了三大弊端:
- 建立连接太慢(高延迟):
- TCP 需要 3 次握手,TLS 还需要 1-2 次握手。在发送数据前,双方得来回跑 2-3 个往返(RTT)。在弱网下,这会导致明显的转圈等待。
- 队头阻塞(Head-of-Line Blocking):
- 虽然 HTTP/2 可以在一个 TCP 连接上同时发多个文件,但 TCP 本质是一个“单通道”有序流。如果其中一个数据包丢了,TCP 会停下来等待重传,导致后续所有文件都堵在后面,即使它们已经到达了。
- 网络切换容易断开:
- TCP 依靠“四元组”(源IP、源端口、目的IP、目的端口)识别连接。当你从 Wi-Fi 切换到 4G 时,IP 变了,TCP 连接必须断开重连。
QUIC 是如何运作的?
QUIC 在 UDP 之上自己实现了一套可靠传输机制(确认应答、重传、拥塞控制),并把 TLS 1.3 直接集成进去。
QUIC 的四大核心特性:
1. 极速握手 (0-RTT / 1-RTT)
QUIC提供0-RTT和1-RTT的连接建立,这意味着QUIC在最佳情况下不需要任何的额外往返时间便可建立新连接。其中更快的0-RTT仅在两个主机之间建立过连接且缓存了该连接的“秘密”(secret)时可以使用。
- 1-RTT:在 TCP 中,建立加密连接需要 TCP 3次握手 + TLS 1.3 握手(总计 2-3 个 RTT)。QUIC 将两者合二为一:客户端在发送第一个 UDP 包(Initial)时,就把 TLS 的
ClientHello塞进去。服务器回一个包就完成了密钥交换。一次往返即可开始传数据。 - 0-RTT:如果客户端之前访问过该服务器,可以缓存服务器的“门票”(Session Ticket)。下次连接时,客户端在第一个包里直接带上加密的应用数据。真正实现“零延迟”发数据。
2. 彻底解决队头阻塞
- 类似SCTP、SSH和HTTP/2,QUIC在同一物理连接上可以有多个独立的逻辑数据流。这些数据流并行在同一个连接上传输,不影响其他流。
- 不同流之间是相互独立的。QUIC的单个数据流可以保证有序交付,但多个数据流之间可能乱序。这意味着单个数据流的传输是按序的,但是多个数据流中接收方收到的顺序可能与发送方的发送顺序不同!
- 如果流 A 的包丢了,只会阻塞流 A,流 B 和流 C 的传输完全不受影响。这彻底解决了 HTTP/2 在 TCP 上的顽疾。
3. 连接迁移 (Connection Migration)
- QUIC 不再使用 IP/端口来识别连接,而是使用一个 64 位的 Connection ID (CID)。
- 场景: 当你拿着手机从电梯出来,Wi-Fi 自动切到 5G,虽然你的 IP 变了,但只要 CID 没变,QUIC 连接就能无缝继续,你刷的视频不会卡顿,也不需要重新登录。
4. 强制内置加密
- 在 TCP 时代,TLS 加密是可选的“插件”。
- 在 QUIC 中,TLS 1.3 是强制内置的。不仅用户数据是加密的,连协议本身的元数据(比如包序号)也被大部分加密,这防止了网络中间设备(如运营商路由器)对协议进行篡改或窥探。
QUIC vs TCP 形象对比
| 特性 | TCP + TLS (HTTP/2) | QUIC (HTTP/3) |
|---|---|---|
| 基础协议 | 基于 IP 的 TCP | 基于 IP 的 UDP |
| 握手耗时 | 2-3 个 RTT | 0-1 个 RTT |
| 多路复用 | 有队头阻塞(一人丢包,全家停步) | 无队头阻塞(一人丢包,他人照跑) |
| 移动性 | 切网络必断,需重连 | 切网络不断,无缝迁移 |
| 安全性 | 外部插件,握手分开 | 原生集成 TLS 1.3,安全性更高 |
为什么要基于UDP而不是新创造一个协议
因为在互联网上部署遭遇很大的困难,创造新型传输层协议的努力基本上都失败了。用户与服务器之间要经过许多防火墙、NAT(地址转换)、路由器和其他中间设备(middle-box),这些设备有很多只认TCP和UDP。如果使用另一种传输层协议,那么就会有N%的连接无法建立,这些中间设备会认为除TCP和UDP协议以外的协议都是不安全或者有问题的。如此高的的失败率一般被认为不值得再做出努力。 另外,网络栈中的传输层协议改动一般意味着操作系统内核也要做出修改。更新和部署新款操作系统内核的过程十分缓慢,需要付出很大的努力。由IETF标准化的许多TCP新特性都因缺乏广泛支持而没有得到广泛的部署或使用。
传输层与应用层协议
IETF版QUIC是一个传输层协议,在该协议之上可以运行其他应用层协议。初始的应用层协议是HTTP/3(h3)。
传输层协议负责连接和数据流处理。
在Google的传统QUIC中,传输层与HTTP融在一起,为包揽一切的全功能设计,它是一个更有指向性的“基于UDP传输HTTP/2帧”(send-http/2-frames-over-udp)的协议。
QUIC报文解析
QUIC 报文分为两大类:长报文头 (Long Header) 和 短报文头 (Short Header)。
1. 长报文头 (Long Header) —— 建立连接专用
长报文头在连接还没完全建立好、或者需要版本协商时使用。
| 字段名称 | 长度 | 用途说明 |
|---|---|---|
| Header Form | 1 bit | 固定为 1,标识这是一个长报文头。 |
| Fixed Bit | 1 bit | 固定为 1,用于防范某些针对协议的攻击,帮助识别 QUIC 流量。 |
| Long Packet Type | 2 bits | 报文子类型:00 Initial(初始), 01 0-RTT, 10 Handshake(握手), 11 Retry(重试)。 |
| Type-Specific Bits | 4 bits | 随类型变化。例如在 Initial 报文中,包含 Packet Number 的长度。 |
| Version | 32 bits | QUIC 版本号。例如 0x00000001 表示 RFC 9000 标准版。 |
| DCID Length | 8 bits | 目标连接 ID (Destination Connection ID) 的长度。 |
| DCID | 0-160 bits | 核心字段:目标连接 ID。用于在路由切换时标识同一个连接。 |
| SCID Length | 8 bits | 源连接 ID (Source Connection ID) 的长度。 |
| SCID | 0-160 bits | 源连接 ID。 |
| Type-Specific Fields | 可变 | 例如 Initial 报文会有 Token(用于防范地址伪造攻击)。 |
| Length | 可变长整数 | 标识该报文剩余部分的字节数(包括 Packet Number 和 Payload)。 |
| Packet Number | 8-32 bits | 报文序列号。注意:在传输时是被加密的,防止中间设备监听。 |
| Payload (Frames) | 可变 | 真正的数据。由一个或多个 Frame(帧) 组成。 |
| 长报文头中有一个明确的 Length 字段。 |
- 作用:这个
Length字段指明了该 QUIC 报文剩余部分(即 Packet Number + Payload)的字节数。 - 为什么要显式指明长度?
因为 QUIC 支持报文合并 (Coalescing)。在一个 UDP 数据包中,可以顺序存放多个 QUIC 报文(例如:一个 Initial 报文后面紧跟一个 Handshake 报文)。有了
Length字段,解析器才知道第一个报文在哪里结束,第二个报文从哪里开始。
2. 短报文头 (Short Header) —— 数据传输专用
连接建立后的常规数据包,为了极致的带宽利用率,去掉了版本、SCID 等冗余信息。
| 字段名称 | 长度 | 用途说明 |
|---|---|---|
| Header Form | 1 bit | 固定为 0,标识这是一个短报文头。 |
| Fixed Bit | 1 bit | 固定为 1。 |
| Spin Bit | 1 bit | 旋转位:用于测量 RTT。客户端和服务器轮流翻转它,中间设备通过观察翻转周期即可知道延迟。 |
| Reserved Bits | 2 bits | 预留,必须为 0。 |
| Key Phase | 1 bit | 密钥相位:用于切换加密密钥,实现密钥的滚动更新。 |
| Packet Number Len | 2 bits | 标识随后的 Packet Number 占用几个字节(1, 2, 或 4 字节)。 |
| DCID | 可变 | 目标连接 ID。 |
| Packet Number | 8-32 bits | 加密的报文序列号。 |
| Payload (Frames) | 可变 | 加密的帧数据。 |
| 短报文头中没有 Length 字段。 |
- 作用:短报文头通常是 UDP 数据包中的最后一个(或唯一一个)报文。
- 如何计算长度:其长度由 UDP 报文的总长度 减去 当前位置之前的偏移量 得到。即:
报文长度 = UDP净荷长度 - 短报文起始位置。
3. 报文内部:Frame(帧)的结构
QUIC 报文的 Payload 部分由多个 Frame 组成。常见的有:
STREAM Frame(应用数据帧)
这是最频繁出现的帧,用于承载 HTTP/3 或其他应用层数据。
| 字段名称 | 长度 | 作用说明 |
|---|---|---|
| Type | 1 byte | 范围 0x08 - 0x0F。低 3 位是标志位:OFF(是否有偏移量)、LEN(是否有长度字段)、FIN(是否是流的终点)。 |
| Stream ID | 可变长整数 | 标识数据属于哪个流(如某个图片请求)。 |
| Offset | 可变长整数 | (可选) 该数据在流中的起始位置。用于处理流乱序到达,重组数据。 |
| Length | 可变长整数 | (可选) Data 字段的长度。 |
| Stream Data | 剩余部分 | 实际的应用层字节流。 |
- 设计精妙处:如果一个报文里只有一个 Stream Frame,
Length可以省略,解析器直接读到包末尾,节省字节。
ACK Frame(确认应答帧)
QUIC 不使用 TCP 的累积确认,而是使用这种高度精简的 Frame 告知发送方收到了哪些包。
| 字段名称 | 长度 | 作用说明 |
|---|---|---|
| Type | 1 byte | 0x02 或 0x03(取决于是否有 ECN 网络拥塞标记)。 |
| Largest Acked | 可变长整数 | 接收方目前收到的最大 Packet Number。 |
| Ack Delay | 可变长整数 | 接收方收到包到发送 ACK 之间故意等待的时间(用于更精准计算 RTT)。 |
| Ack Range Count | 可变长整数 | 表示后面有多少个非连续的确认范围。 |
| First Ack Range | 可变长整数 | 表示从 Largest Acked 往下数,连续确认了多少个包。 |
| Gap & Range | 可变长整数 | (重复字段) 用于描述中间“丢包”造成的空洞。 |
- 设计精妙处:通过
Gap和Range的组合,一个 ACK Frame 就能表达出“我收到了1-10和15-20,但没收到11-14”,极大提高了处理丢包的效率。
CRYPTO Frame(加密握手帧)
专门用于承载 TLS 握手数据。
| 字段名称 | 长度 | 作用说明 |
|---|---|---|
| Type | 1 byte | 固定为 0x06。 |
| Offset | 可变长整数 | 握手数据的偏移量。 |
| Length | 可变长整数 | 握手数据的长度。 |
| Crypto Data | 剩余部分 | 实际的 TLS 记录(如 ClientHello)。 |
- 区别:它和 STREAM Frame 很像,但没有 Stream ID,因为握手数据被视为一个全局的单一流。
CONNECTION_CLOSE Frame(连接关闭帧)
当发生协议错误或主动断开连接时使用。
| 字段名称 | 长度 | 作用说明 |
|---|---|---|
| Type | 1 byte | 0x1c(QUIC层错误)或 0x1d(应用层错误)。 |
| Error Code | 可变长整数 | 错误代码(如 NO_ERROR, PROTOCOL_VIOLATION)。 |
| Frame Type | 可变长整数 | 触发错误的那个帧的类型(仅限 0x1c 类型)。 |
| Reason Phrase Len | 可变长整数 | 错误原因文本字符串的长度。 |
| Reason Phrase | 可变 | 可读的错误描述字符串。 |
NEW_CONNECTION_ID Frame(连接迁移核心帧)
这是实现“Wi-Fi 切 4G 不掉线”的关键。
| 字段名称 | 长度 | 作用说明 |
|---|---|---|
| Type | 1 byte | 固定为 0x18。 |
| Sequence Number | 可变长整数 | 这个 ID 的序号。 |
| Retire Prior To | 可变长整数 | 要求对方停止使用比此序号小的旧 ID。 |
| Length | 1 byte | 新 CID 的长度。 |
| Connection ID | 可变 | 服务器分发给客户端的新 ID,用于后续路径切换。 |
| Stateless Reset Token | 16 bytes | 即使服务器崩溃,也能凭此 Token 识别并重置连接。 |
PADDING(填充帧)
PADDING (0x00):全 0 填充。用于将初始报文凑齐 1200 字节(MTU 探测,防止放大攻击)。
什么是放大攻击 (Amplification Attack)?
- UDP 的特性:UDP 是无连接的,发送端可以轻易伪造“源 IP 地址”。TCP由于需要三次握手确认连接,就不会受到放大攻击。
- 攻击原理:
- 攻击者 向一个 服务器 发送一个非常小的请求(例如 10 字节),但把数据包里的“源 IP”改成 受害者 的 IP。
- 服务器 收到请求后,如果返回一个非常大的响应(例如 1000 字节)。
- 结果:受害者并没发请求,却收到了巨大的流量。攻击者利用服务器的响应,将流量“放大”了 100 倍。
- QUIC 的风险:在 QUIC 握手初期,客户端发一个
Initial报文(ClientHello),服务器会返回证书等大量信息。如果不加限制,QUIC 就会变成一个完美的放大攻击工具。
为了解决上述问题,QUIC 协议(RFC 9000)强制规定:所有 Initial 报文(初始握手报文)的 UDP 净荷长度必须至少为 1200 字节。
如果你的实际数据(握手信息)没那么大,就必须用 PADDING 帧 填满。
PADDING 的作用:
- 限制放大倍数 (Anti-Amplification):
- 服务器在验证客户端 IP 真实性之前,发送的数据量不能超过接收数据量的 3 倍。
- 如果客户端发了一个 1200 字节的包(含 PADDING),服务器就可以合法地回传最多 3600 字节。
- 这迫使攻击者如果想发起攻击,自己也得耗费同样巨大的带宽,攻击者的“杠杆”消失了,攻击也就失去了意义。
- 路径 MTU 探测 (MTU Discovery):
- 互联网路径上最小的 MTU(最大传输单元)通常是 1280 字节(IPv6 的最低要求)。
- 如果一个 1200 字节的 QUIC 包(含 PADDING)能安全到达服务器,说明这条路径支持大气泡传输。
- 如果发不出去,QUIC 会立刻发现网络环境有问题,从而避免了后续大数据包频繁被分片或丢弃。
其他常用控制帧 (简述)
- PING (0x01):保持连接活跃,探测路径可达性。
- RESET_STREAM (0x04):发送方通知接收方:“我不发这个流了,你可以把它丢掉”。
- MAX_DATA (0x10):连接级流量控制,告诉对方:“你在这个连接里总共只能发这么多字节”。
- MAX_STREAM_DATA (0x11):流级流量控制。
- PATH_CHALLENGE (0x1a):连接迁移时发出的“挑战包”,包含 8 字节随机数。
- PATH_RESPONSE (0x1b):对方回显随机数,证明新路径确实通畅。
可变长度字段的判断原理
QUIC 几乎所有涉及“长度”和“数值”的字段(如 Length, Stream Offset, Stream ID 等)都使用了一种精巧的可变长度整数编码 (Variable-Length Integer Encoding)。
前 2 位标识法
任何一个可变长度的整数,它的前 2 个 bit(最高有效位)决定了整个数值占用多少个字节:
| 前 2 位 (Binary) | 剩余长度 | 总字节数 | 取值范围 |
|---|---|---|---|
| 00 | 0 字节 | 1 字节 | 0 到 63 () |
| 01 | 1 字节 | 2 字节 | 0 到 16,383 () |
| 10 | 3 字节 | 4 字节 | 0 到 1,073,741,823 () |
| 11 | 7 字节 | 8 字节 | 0 到 |
解析流程示例:
当解析器读到一个字节,先看前 2 位。如果是 01,解析器就知道“哦,这还没完,我得再读 1 个字节”,然后把这 2 个字节合并,去掉前 2 位标识位,剩下的 14 位就是真实的数值。
特殊可变长字段
除了通用的整数编码,还有几个关键字段有特殊的判断方式:
1. Connection ID (CID) 的长度
- 在长报文头中:有专门的
DCID Len和SCID Len字段(各占 1 字节)。解析器先读这 1 字节的长度,再根据长度读取后续的 ID。 - 在短报文头中:短报文头里没有长度字段。解析器必须依靠“上下文”知道 CID 的长度。
- 原理:在握手阶段,双方会协商好 CID 的长度。当连接进入数据传输阶段(使用短报文)时,解析器查一下本地保存的该连接的状态,就知道该读多少字节了。
2. Packet Number (PN) 的长度
Packet Number 的长度是动态变化的(为了节省带宽,1 到 4 字节不等)。
- 判断方法:它隐藏在报文头的第一个字节(Type Byte)中。
- 具体规则:第一个字节的最后 2 位(bits 6-7)决定了 PN 的长度。
00→ 1 字节01→ 2 字节10→ 3 字节11→ 4 字节
- 注意:由于 PN 头是加密的,解析器必须先根据之前交换的密钥对头进行“去掩码”处理,才能读到这两位。
3. Stream Frame 中的数据长度
在数据帧中,通常有一个 Length 字段。
- 如果 Frame 位于报文的末尾,
Length字段可以省略,解析器直接读到报文结束。 - 如果不是末尾,
Length字段同样使用上述的 2位标识法 可变整数编码。
QUIC生命周期
第一阶段:连接建立与握手(长报文头阶段)
在这个阶段,QUIC 使用 Long Header(长报文头),因为双方需要交换版本信息、连接 ID(CID)并验证身份。
1. Initial Packet(初始报文)
- 发送方:客户端 服务器。
- 内含帧:
CRYPTO:包含 TLS 1.3 的ClientHello。PADDING:强制填充到 1200 字节,防止放大攻击。
- 目的:发起连接,开始密钥交换。
2. Retry Packet(重试报文 - 可选)
- 发送方:服务器 客户端。
- 用途:如果服务器怀疑客户端 IP 是伪造的,会发一个带有
Token的 Retry 包。客户端必须用这个Token重新发一个Initial包,以证明自己能收到回包。
3. Initial + Handshake Packet(服务器响应)
- 发送方:服务器 客户端。
- 内含帧:
ACK:确认收到客户端的 Initial。CRYPTO:包含ServerHello。Handshake报文:包含证书(Certificate)和握手完成(Finished)信号。
- 目的:确立加密参数,完成服务器身份验证。
4. 0-RTT Packet(预热数据 - 可选)
- 发送方:客户端 服务器。
- 前提:非首次连接,客户端缓存了服务器的 Token。
- 内含帧:
STREAM帧。 - 目的:在握手还没彻底完事时,直接发业务数据,实现零延迟。
0-RTT Packet一般会和Initial Packet合并后一起发送。
一个典型的 0-RTT 启动包(UDP 包)看起来是这样的:
+-----------------------------------------------------------------------+
| UDP Header (Dest Port: 443, etc.) |
+-----------------------------------------------------------------------+
| QUIC Initial Packet |
| - Frame: CRYPTO (ClientHello + Session Ticket) |
| - Frame: PADDING (为了安全凑长度) |
+-----------------------------------------------------------------------+
| QUIC 0-RTT Packet (紧跟在 Initial 后面) |
| - Frame: STREAM (HTTP GET /index.html) |
+-----------------------------------------------------------------------+第二阶段:数据传输(短报文头阶段)
一旦握手完成(双方收到了 Finished 信号),连接进入稳定期。此时为了降低开销,全部切换为 Short Header(短报文头)。
5. 1-RTT Packet(正常数据报文)
- 发送方:双方互发。
- 特点:报文头极其精简,去掉了 SCID 和版本号。
- 内含帧:
STREAM:真正的网页请求、图片数据等。ACK:对收到的数据进行确认。MAX_DATA / MAX_STREAM_DATA:告诉对方你还能发多少数据(流量控制)。PING:如果没数据发了,定期发个包保活。
第三阶段:连接迁移(保持连接阶段)
这是 QUIC 的“绝活”。当用户网络变化(如 Wi-Fi 变 5G)时,报文会发生如下演变:
6. 1-RTT Packet (New Path)
- 现象:客户端从新的 IP/端口发出了一个 1-RTT 报文,但 Connection ID 依然是旧的。
- 服务器动作:识别出 CID,知道是老用户换了地址,但需要验证。
7. Path Challenge / Response Packet
- 发送方:服务器 客户端(Challenge),客户端 服务器(Response)。
- 内含帧:
PATH_CHALLENGE(8字节随机数)和PATH_RESPONSE。 - 目的:服务器确认这个新 IP 确实属于这个客户端,而不是黑客伪造的。验证通过后,数据继续传输。
8. NEW_CONNECTION_ID Packet
- 发送方:双方互发。
- 用途:为了防止网络监听者通过 CID 追踪用户,QUIC 会在传输过程中不断通过这个帧分发“备用 CID”。换网络时,客户端可以换上一个新的备用 CID。
第四阶段:连接关闭(终结阶段)
连接不能永远维持,必须优雅或强制地关闭。
9. Connection Close Packet
- 发送方:主动关闭方 被动方。
- 内含帧:
CONNECTION_CLOSE。 - 目的:告诉对方“我要走了”,并带上错误码(比如
NO_ERROR或PROTOCOL_VIOLATION)。发送后,该连接的状态被销毁。
10. Stateless Reset Packet(无状态重置)
- 场景:服务器宕机重启了,客户端发来一个属于旧连接的包。服务器完全不认识这个包。
- 发送方:服务器 客户端。
- 特点:它不是一个标准的加密报文,而是一个带有特殊 Token 的 UDP 包。
- 目的:硬性终止。告诉客户端:“我不记得这个连接了,别发了,请重新开始”。
QUIC对比TCP
一、 QUIC 相比 TCP 的核心优势
1. 极致的低延迟(握手与 0-RTT)
- TCP: 即使是 TLS 1.3,也需要 TCP 握手 + TLS 握手,至少 1 个 RTT。旧版本甚至需要 2-3 个 RTT。
- QUIC: 将传输和加密握手合并。首次连接 1-RTT,后续连接 0-RTT。在跨境访问或高延迟网络下,QUIC 的首字节时间(TTFB)远快于 TCP。
2. 彻底解决队头阻塞 (Head-of-Line Blocking)
- TCP: 就像一队士兵排队过窄桥,一个人摔倒了,后面所有人都要停下来等。这是 TCP 的单流有序特性决定的。
- QUIC: 像多条并行的车道。一个数据包丢了,只会导致那一条“流”停顿,其他流(比如网页里的不同图片)可以照常传输。
3. 连接迁移 (Connection Migration)
- TCP: 手机 Wi-Fi 切 5G,连接必断,必须重新握手、重新登录、重新加载。
- QUIC: 使用 Connection ID。网络切换时,物理地址变了,但逻辑连接不断。这是移动互联网时代的“杀手锏”。
4. 迭代速度快 (用户态实现)
- TCP: 实现在操作系统内核中。更新一个拥塞控制算法(如 BBR)需要升级 Linux 内核,周期以“年”计。
- QUIC: 实现在用户态(应用程序里)。像 Chrome 或抖音,更新一下 App 就能升级 QUIC 算法,迭代周期以“周”计。
二、 QUIC 的缺点(为什么 TCP 依然不可或缺)
1. CPU 开销更高
- 原因: TCP 经过了几十年的优化,很多网卡硬件直接支持 “TCP 卸载”(TOE),由硬件处理分包和计算。
- QUIC: 基于 UDP,且几乎所有数据都经过强力加密。这导致处理相同数据量时,QUIC 消耗的 CPU 资源通常比 TCP 高出 10%~20%。对于大型数据中心来说,这意味着更高的电费和服务器成本。
2. 中间设备的不友好(UDP 屏蔽)
- 现状: 许多公司的防火墙、商场的路由器、甚至某些运营商会因为安全策略(防 DDoS)直接丢弃或限制 UDP 443 端口的流量。
- 后果: 如果 QUIC 走不通,协议必须有回退机制(Fallback),自动切回 TCP。
3. 协议复杂度极高
- TCP 是简单的“流”,实现起来容易。
- QUIC 包含了传输、加密、多路复用、流量控制等一系列逻辑,实现一个稳定、高性能的 QUIC 协议栈难度极大。目前市面上成熟的实现(如 quic-go, quiche, MsQuic)大多掌握在大厂手中。
4. 对小流量并不经济
- 如果你只是发送一个极小的传感器数据(IoT),TCP 或原生 UDP 的开销更低。QUIC 复杂的握手和加密头反而显得多余。
三、 深度对比表
| 特性 | TCP (HTTP/2) | QUIC (HTTP/3) | 评价 |
|---|---|---|---|
| 基础协议 | TCP (内核态) | UDP (用户态) | QUIC 迭代快,TCP 性能稳 |
| 握手延迟 | 1~3 RTT | 0~1 RTT | QUIC 胜 (快) |
| 队头阻塞 | 存在 (影响整条连接) | 不存在 (流级别隔离) | QUIC 胜 (弱网表现好) |
| 连接迁移 | 不支持 (断线) | 支持 (无缝) | QUIC 胜 (移动端福音) |
| CPU 占用 | 较低 (硬件优化成熟) | 较高 (软件加密开销) | TCP 胜 (省资源) |
| 网络兼容性 | 极佳 (全世界通畅) | 一般 (部分防火墙拦截) | TCP 胜 (兼容性) |
| 安全性 | TLS 可选 (通常有) | 强制集成 TLS 1.3 | QUIC 胜 (默认安全) |
四、 总结:QUIC 会完全替代 TCP 吗?
结论是:不会。
- 分层共存:
- QUIC 将主霸占 “人机交互” 场景:网页浏览、短视频(TikTok/抖音)、直播、实时游戏。这些场景对延迟和网络切换极其敏感。
- TCP 将继续霸占 “机器对机器” 场景:数据库连接、后端微服务调用、大数据传输、遗留的工业控制系统。这些场景更看重吞吐量的稳定和低 CPU 消耗。
- 基础设施限制: 只要还有旧设备、旧防火墙存在,TCP 就是互联网的“保底协议”。
- 未来的趋势: 我们正在进入一个 “双协议并行” 的时代。现代浏览器(Chrome/Safari)和 App 都会优先尝试 QUIC,如果走不通,无缝切换到 TCP。