什么是QUIC
QUIC(Quick UDP Internet Connections)是一种由Google开发的新型网络传输协议,旨在通过UDP提供与TCP类似的可靠传输服务,同时解决传统TCP协议的延迟和拥塞控制问题。QUIC整合了TLS加密、流量控制、连接管理等功能,优化了传输性能,尤其是在高延迟和不稳定网络环境中表现出色。
QUIC所处的网络层次如下图所示。

QUIC的核心概念
连接ID:QUIC使用连接ID来标识连接,从而使得在网络地址(如IP和端口)变化时能够保持连接状态。
数据包:QUIC的数据包结构与传统UDP包不同,包含连接ID、包号、加密信息等,确保数据包的安全和可靠传输。
流(Stream):QUIC中的流是数据传输的基本单位,每个流都有唯一的ID,可以独立传输,避免互相影响。
握手过程:QUIC将连接建立与TLS握手结合,使用1-RTT或0-RTT完成握手过程,极大地减少了连接延迟。
QUIC的数据包格式

QUIC的数据包分为Header和Data部分,其中Header是明文传输,包括Flags是标志位,Connection ID是连接ID,可用于连接迁移,QUIC Version是QUIC的版本号,Packet Number是包序号,用于保证可靠传输;Data部分是密文传输,是一些数据帧,有很多数据帧类型:Stream、ACK、Padding、Blocked等,其中Stream帧传输应用数据。
Stream

Frame Type: Bit7~Bit0
- Bit7:必须设置为1,表示Stream帧
- Bit6:如果设置为1,表示发送端在这个stream上已经结束发送数据,流将处于半关闭状态
- Bit5:如果设置为1,表示Stream头中包含Data Length字段
- Bit432:表示offset的长度。000表示0字节,001表示1字节,002表示2字节,以此类推
- Bit10:表示Stream ID的长度。00表示1字节,01表示2字节,10表示3字节,11表示4字节
建立连接
建立HTTPS连接
先分析一下HTTPS的握手过程,包括TCP握手和TLS(Transport Layer Security)握手,HTTPS连接耗时3个RTT。

QUIC基于TLS建立连接
QUIC实现了快速握手,并把握手过程分为两种情况,分别是1-RTT和0-RTT。

第一次握手:
- 客户端主动向服务器发送Inchoate CHLO报文
- 服务器会向客户端发送REJ报文。REJ报文包含了服务器的配置信息,如长期的Diffie-Hellman值,服务器配置的签名,source-address-token(stk, 用于验证的加密块,包含有服务器看到的客户端的IP地址和服务器当前的时间戳,之后客户端会将该stk发回)等,为了进行身份证明还会使用私钥进行签名,同时也可以防篡改;
- 在收到服务器的配置信息后,客户端会通过证书链机制验签,并实现对服务器的身份认证。
第二次握手: - 客户端在通过对服务器的验证之后,客户端会生成一个Diffie-Hellman值。此时客户端有了自身和对方的Diffie-Hellman值,就可以计算出初始密钥(initial key, ik);
- 客户端将包含有DH公开之的明文Complete CHLO发送至服务器;
- 客户端使用ik对请求数据加密,发送至服务器;
- 服务器收到Complete CHLO之后就可以获得客户端的Diffie-Hellman的值,就可以计算出初始密钥。
- 服务器立即向客户端发送SHLO报文(ik加密的)。SHLO报文含有一个服务器临时Diffie-Hellman值,可以用于计算前向安全的密钥(会话密钥);
- 服务器收到加密的请求数据,使用初始密钥进行解密;
- 服务器使用会话密钥对响应数据进行加密,发回给客户端。
- 客户端在收到SHLO之后使用初始密钥解密得到服务器的临时DH公开值,根据该临时值计算出会话密钥;
- 客户端收到加密的响应数据后,使用会话密钥进行解密。
整个握手过程会在2个RTT内完成。
0-RTT
客户端在重连同一个服务器时,会使用已经缓存的服务器相关配置信息(stk,DH公开值等信息),并直接向服务器发送Complete CHLO报文,并使用ik对请求报文进行加密。但是服务器方面会标识相应的stk等信息已经过期,这时服务器会发送REJ信息,客户端需要重新与服务器进行连接。
如果没有过期的话,就可以直接建立连接,省下了重新建立连接的开销。
前向安全性
所谓前向安全性就是指,在最后一次握手时,会生成一个会话密钥sk。这样即使服务器的长期DH值被破获,且生成了初始密钥ik,也无法对会话中的数据进行解密。
多路复用机制
基于TCP的应用程序会在TCP单字节流抽象层中实现多路复用。为了避免由于TCP顺序传递导致的头部阻塞(head-of-line blocking),QUIC支持在单个UDP连接中复用多个流,并保证UDP报文的丢失仅影响相应的流,而不会影响其他的流(stream)。
可以在QUIC流上构建任意大小的应用程序报文,最多支持2的64次方的字节。并且stream的实现是轻量级的,即使消息报文很小也可以为它们使用单独的流。每一个Stream都有stream ID唯一标识。这些流ID由客户端/服务器进行静态分配。客户端主动发起的流的ID永远是奇数,服务器发起的流的ID是偶数。这样可以避免冲突。当在一个未使用过的流上发送数据时,流会自动创建;当需要关闭时,就会在最后一帧数据上设置一个FIN的标志指示接收方关闭流。如果发送方或接收方确定不再需要流上的数据,则可以取消流,而无需断开整个 QUIC 连接。尽管流是可靠的抽象,但 QUIC 不会为已取消的流重新传输数据。
连接迁移
QUIC连接使用随机生成的64bit的cid唯一确定。cid允许客户机在网络之间漫游,而不受网络或传输层参数变化的影响。
cid使得客户端能够独立于网络地址转换(network address translation, NAT)之外。cid 在路由中起着重要作用,特别是用于连接标识的目的。此外,使用 cids 可以通过探测连接的新路径实现多路径。
在连接迁移期间,端点假设对等方愿意在其当前地址接受数据包。因此,端点可以迁移到新的 IP 地址,而无需首先验证对等方的 IP 地址。新的路由路径可能不支持端点的当前发送速率。在这种情况下,端点需要重新构建它的拥塞控制器。另一方面,从一个新的对等地址接收非探测包 ,确认对等地址已迁移到新的 IP 地址。

