我们为什么需要QUIC?
QUIC协议学习笔记
背景
万维网(World Wide Web, WWW)是一项伟大的发明,由英国计算机科学家蒂姆·伯纳斯-李(Tim Berners-Lee)于1989年提出,并在1990年成功开发出第一个网页浏览器和服务器。当时他在欧洲核子研究中心(CERN)工作,目标是解决科研人员跨平台、跨国界的信息共享难题。如今我们依然可以通过CERN提供的网站(The World Wide Web project)来探究第一个万维网项目是如何工作的。
HTTP(HyperText Transfer Protocol)是万维网的核心通信协议,旨在通过互联网实现超文本资源的分布式传输。作为应用层协议,HTTP基于TCP/IP协议栈,定义了客户端(如浏览器)与服务器之间的通信规则,支持文本、图像、视频等任意数据类型的传输。其核心特点是无状态性(服务器不保留会话信息)和请求-响应模型(客户端主动发起请求,服务器返回结果)。
随着互联网技术的发展,人们对于信息交互的需求也逐渐发生变化。这种变化主要是体现在三方面,一是数据类型越来越多,最初可能只需要利用互联网交互文本信息,后来需要交互图片和视频数据;二是数据传输速度的要求越来越高,最初只需要解决传输的数据可靠性问题,后来逐渐对数据的传输速度有了进一步的需求;三是数据安全性要求越来越高。为此,HTTP协议也历经了多个版本的更迭,每个版本针对性能、功能或安全性进行了优化。下面我们主要关注数据传输速度和安全方面的演变进程。在协议的更新迭代过程中,多方面的优化可能是同时进行的,但是下面会分开进行梳理,以便于理解。
-
数据传输速度
-
HTTP/0.9(1991年)
- 无持久连接。一次请求完成后连接即关闭,发送新的请求需要建立新的连接。
-
HTTP/1.0(1996年)
- 显氏持久连接(通过Connection: keep-alive实现)。但是该版本中短链接仍然是默认模式(可能是考虑到浏览器的兼容性问题),即每次请求需重新建立 TCP 连接,完成响应后立即关闭。虽性能较低,但通过非标准字段 Connection: keep-alive 可启用长连接复用。
-
HTTP/1.1(1999年)
-
默认持久连接。相较于HTTP/1.0中默认短链接,通过指定字段实现持久连接,HTTP/1.1 默认启用持久连接(Connection: Keep-Alive),允许在一个 TCP 连接上发送多个请求和响应,避免了 HTTP/1.0 每次请求都需要新建和关闭 TCP 连接的巨大开销。
-
管道化请求(Pipelining)。允许客户端在未收到前一个请求响应时发送后续请求,但服务器仍需按请求顺序返回响应。这种机制引入了新的问题:队头阻塞(HOL Blocking)问题,若前序请求响应延迟,后续请求会被阻塞。例如客户端发送请求 A → B → C,服务器处理顺序为 A(耗时 3s)→ B(1s)→ C(1s),则总耗时为 5s(而非 3s)。
-
既然请求存在队头阻塞问题,那为什么不多建立几个连接,每个请求走一个连接呢?这种方法在该版本的协议中是可行的,但是浏览器限制了同一域名的最大连接数量(好像是6个)。那为什么要限制?第一,每个连接都是一个TCP连接,由于一台服务器可能要面向成千上万的客户端,建立太多的连接会增加服务器的负担。第二,由于TCP存在拥塞控制机制,TCP连接之间存在带宽竞争的问题,连接越多越难实现带宽收敛。
-
-
HTTP/2(2015年)
-
二进制分帧。这一点先按下不详细解释,如果后续有需要,再进一步补充。
-
多路复用。针对上一版本中队头阻塞的问题,这一版本中引入了流的概念,从而实现了在一个TCP连接上的多路复用。具体来说就是让每个http请求都属于一个流(多个请求可能属于一个流),每个流都有一个流ID。相同流的请求按照先后顺序进行响应,不同流的请求就不存在队头阻塞问题。
-
-
HTTP/3(2020年草案)
- 弃用TCP,采用基于UDP的QUIC协议,解决TCP队头阻塞问题,提升弱网环境下的连接速度和稳定性。
-
-
安全性
HTTP的安全性的演变主要就是SSL/TLS的进化,SSL是TLS的前身,下面将从TLS 1.2开始梳理。数据的安全主要是涉及两方面,一是加解密算法的安全性。二是密钥交换的安全性。
-
加解密算法
-
SSL协议时代(1994-1999年)
- SSL 1.0的诞生(1994年)
- 网景公司(Netscape)为解决早期HTTP明文传输的安全问题,开发了SSL(Secure Sockets Layer)协议,但SSL 1.0因安全漏洞从未公开发布。
- SSL 2.0与早期应用(1995年)
- 首个公开版本SSL 2.0发布,支持简单的数据加密和身份验证,但存在严重安全缺陷(如弱加密算法),仅短暂应用于网景浏览器。
- SSL 3.0的成熟(1996年)
- SSL 3.0重构了协议设计,引入更安全的握手流程和加密算法(如SHA-1),成为后续TLS协议的基础。此时HTTPS开始被银行和电商采用。
- SSL 1.0的诞生(1994年)
-
TLS协议时代:(1999-)
- TLS 1.0的推出(1999年)
- 因SSL命名权争议,IETF将SSL 3.1更名为TLS 1.0(RFC 2246),保留SSL核心逻辑但优化了加密套件和密钥交换机制,成为首个开放标准。
- TLS 1.1与安全补丁(2006年)
- 针对CBC模式攻击等问题,TLS 1.1引入显式初始化向量(IV)和更严格的错误处理,提升对中间人攻击的防御能力。
- TLS 1.2的现代化升级(2008年)
- 支持AES-GCM等强加密算法,废除MD5/SHA-1哈希函数,引入可扩展的加密套件协商机制,奠定现代HTTPS的技术基础。
- TLS 1.3的性能与安全飞跃(2018年)
- 简化握手流程(1-RTT甚至0-RTT),废除不安全的算法(如RSA密钥交换),支持前向安全(Forward Secrecy)。
- TLS 1.0的推出(1999年)
-
why QUIC?
Web 延迟仍然是改善用户体验的障碍。与此同时,互联网正迅速从不安全的流量(HTTP)转向安全的流量(HTTPS),这增加了延迟。降低底层传输机制延迟的努力通常会遇到 TLS/TCP 生态系统的限制。
- 天下苦TCP久矣。TCP的发明适用于解决数据传输的可靠性问题,但拥塞控制、丢包重传机制限制了数据传输的速度。此外,TCP协议栈通常与操作系统内核绑定在一起,升级或者修改TCP协议栈需要升级操作系统。
- 握手延迟的代价。TCP 和 TLS 的通用性很好地服务于互联网的发展,但分层的成本越来越明显。在发送任何应用程序数据之前,TCP 连接通常会产生至少1RTT延迟,TLS 需要2RTT延迟。互联网上的大多数连接,当然还有网络上的大多数交易,都是短距离传输,最容易受到不必要的握手往返的影响。
- 队头阻塞问题。HTTP和TCP都存在队头阻塞问题,前者是应用层的问题在HTTP/2中已经得到一定程度的改善,后者是传输层的问题,仍然没有得到有效解决。
QUIC的设计
QUIC的传输层协议是UDP,因为UDP已经被运营商所支持,不存在兼容性问题。
连接建立
传统的TLS+TCP的连接建立过程:
即使不算DNS的时间,如果只需要发送一个很小的数据包,数据传输只需要1-RTT,但是连接的建立需要3-RTT。
QUIC基于UDP,虽然传输层无需建立连接,但是仍要进行传输层的协商以实现流量控制等功能。QUIC将传输层协商和加密握手整合在一起以避免客户端和服务器端在建立连接阶段的多次握手。
传输层协商:协商 QUIC 版本号、协商 quic 传输参数、生成连接 ID、确定 Packet Number 等信息,类似于 TCP 的 SYN 报文;保证通信的两端确认过彼此,是对的人。
TLS1.3 握手:标准协议,非对称加密,目的是为了协商出 对称密钥,然后后续传输的数据使用这个对称密钥进行加密和解密,保护数据不被窃取。
客户端会缓存建立的连接的密钥信息,下次直接进行通信,无需重复完整的连接建立过程,这就是所说的0-RTT。
数据封装
QUIC中吸取了HTTP/2中引入的流和帧的理念。按照HTTP/2,引入流的概念可以在一定程度上解决应用层队头阻塞问题,一个流的数据丢包不影响另一个流的数据解析以及交付给应用层。在QUIC中,流的作用还不仅仅如此,后面还会讲到拥塞控制也是基于流的颗粒度来进行的。
上图是一个典型的QUIC数据包。
一个数据包中可能包含多个帧。
数据帧有很多类型:Stream、ACK、Padding、Window_Update、Blocked 等,通过Frame Type进行指定和区分。
可靠传输
QUIC 是基于 UDP 协议的,而 UDP 是不可靠传输协议,那 QUIC 是如何实现可靠传输的呢?其实可靠传输的内在本质就是:发送方要重新发送接收方没收到的数据包,直到接收方接收到完整的数据。当然,QUIC有多种触发重传的机制,比如快速重传、超时重传、前向纠错重传等。
可靠传输有 2 个重要特点:
(1)完整性:发送端发出的数据包,接收端都能收到
(2)有序性:接收端能按序组装数据包,解码得到有效的数据
问题 1:发送端怎么知道发出的包是否被接收端收到了?
解决方案:通过包号 Packet number(PKN)和确认应答(SACK)
(1)客户端:发送 3 个数据包给服务器(PKN = 1,2,3)
(2)服务器:通过 SACK 告知客户端已经收到了 1 和 3,没有收到 2
(3)客户端:重传第 2 个数据包(PKN=4)
由此可以看出,QUIC 的数据包号是单调递增的。也就是说,之前发送的数据包(PKN=2)和重传的数据包(PKN=4),虽然数据一样,但包号不同。
问题 2:既然包号是单调递增的,那接收端怎么保证数据的有序性呢?按照上面的例子,2丢失,重传之后的数据包是4,接收端怎么知道应该将4放在3前面而不是3后面呢?
解决方案:通过数据偏移量 offset,每个帧都包含流ID和 offset 字段,按照offset进行排序即可。
流量控制
QUIC和TCP类似,使用滑动窗的方法进行发送窗口控制。和TCP不同的是,QUIC的流量控制分为两个级别,一个是与TCP类似的连接级别的,另一个是流级别的。
为什么要进行流级别的流量控制?主要是为了解决传输层的队头阻塞问题。
QUIC 是如何解决 TCP 层的队头阻塞问题的呢?其实很简单,HTTP/2 之所以存在 TCP 层的队头阻塞,是因为所有请求流都共享一个滑动窗口,那如果给每个请求流都分配一个独立的滑动窗口,是不是就可以解决这个问题了?
QUIC 允许单个连接内并发传输多个独立的数据流(如网页资源、视频分片、API 请求等),每个流对应不同的应用逻辑或优先级。流级别的流量控制通过以下方式优化资源分配:
-
防止单一流垄断资源
若某个流(如大文件下载)持续高速发送数据,可能挤占其他流(如实时视频)的带宽或接收缓冲区,导致延迟或卡顿。流级控制可独立限制每个流的发送速率,确保公平性。示例:
流A(视频流)的最大窗口设为 1MB,流B(文件下载)设为 10MB,优先保障视频流的实时性。
当流B的发送速率超过接收方处理能力时,仅阻塞流B,不影响流A的正常传输。
-
动态优先级调整
应用层可为不同流设置优先级标签(如高、中、低),流级控制根据标签动态分配窗口大小。例如:
高优先级流(如用户交互请求)快速扩展窗口,低优先级流(如后台日志)缓慢增长窗口。
拥塞控制
流量控制通常是指应用层的控制数据发送速率,拥塞控制通常指传输层的数据发送速率控制。前者是为了避免接收方缓冲区溢出,后者是为了避免网络拥塞。
QUIC和TCP的拥塞控制机制虽然在目标上一致(防止网络过载),但在设计理念、实现方式和性能优化上存在显著差异。以下是两者的核心区别:
-
算法可插拔性
QUIC允许应用层动态选择拥塞控制算法(如BBR、CUBIC、Reno等),开发者可根据网络场景灵活调整。例如,在视频直播中启用BBR以优化带宽利用率,在文件传输中使用CUBIC保证公平性。
TCP的拥塞控制算法通常由操作系统内核实现(如Linux默认使用CUBIC),用户态无法动态切换。
-
控制粒度
QUIC实现了流级拥塞控制,每个流(Stream)独立管理拥塞窗口,避免单一流拥塞影响全局。例如,视频流因丢包受限时,音频流仍能继续传输。
TCP仅支持连接级拥塞控制,所有数据流共享同一拥塞窗口,丢包会导致整个连接速率下降。
连接迁移
当客户端切换网络时,和服务器的连接并不会断开,仍然可以正常通信,对于 TCP 协议而言,这是不可能做到的。因为 TCP 的连接基于 4 元组:源 IP、源端口、目的 IP、目的端口,只要其中 1 个发生变化,就需要重新建立连接。但 QUIC 的连接是基于 64 位的 Connection ID,网络切换并不会影响 Connection ID 的变化,连接在逻辑上仍然是通的。

假设客户端先使用 IP1 发送了 1 和 2 数据包,之后切换网络,IP 变更为 IP2,发送了 3 和 4 数据包,服务器根据数据包头部的 Connection ID 字段可以判断这 4 个包是来自于同一个客户端。QUIC 能实现连接迁移的根本原因是底层使用 UDP 协议就是面向无连接的。