Skip to content

QUIC工作原理

连接

QUIC连接是两个QUIC端点之间的单次会话(conversation)过程。QUIC建立连接时,加密算法的版本协商与传输层握手合并完成,以减小延迟。

在连接上实际传输数据时需要建立并使用一个或多个数据流。

连接ID(Connection ID)

每个连接过程都有一组连接标识符,或称连接ID,该ID用以识别该连接。每个端点各自选择连接ID。每个端点选择对方使用的连接ID。

连接ID的基本功能是确保底层协议(UDP、IP及其底层协议)的寻址变更不会使QUIC连接传输数据到错误的端点。

利用连接ID的优势,连接可以在IP地址和网络接口迁移的情况下得到保持——而这TCP永远做不到。举例来说,当用户的设备连接到一个Wi-Fi网络时,将进行中的下载从蜂窝网络连接转移到更快速的Wi-Fi连接。与此类似,当Wi-Fi连接不再可用时,将连接转移到蜂窝网络连接。

端口号

QUIC基于UDP建立,因此使用16比特的UDP端口号字段来区分传入的不同连接。

版本协商

客户端的QUIC连接请求会告知服务器所希望使用的QUIC协议版本,服务器端会回复一系列支持的版本供客户端选择。

使用TLS的连接

在初始的数据包建立连接之后,连接发起者会马上发一个加密的帧以开始安全层握手。安全层使用TLS 1.3协议。 在QUIC中,没有方法或机制避免使用TLS连接。该设计旨在使中间设备难以篡改数据包,防止协议僵化。

数据流

数据流(Streams)在QUIC中提供了一个轻量级、有序的字节流的抽象化。

QUIC中有两种基本的数据流类型:

  • 从发起者到对等端(Peer)的单向数据流。
  • 双向均可发出数据的双向数据流。

连接端点的任意一方都可以建立这两种数据流,数据流之间可并行、交错地传输,并且可以被取消。

通过QUIC发送数据需要建立一个或多个数据流。

流量控制(Flow control)

每个数据流都有独立的流量控制,端点可以通过此实现内存控制和反压(back pressure)。数据流的创建本身也有流量控制,连接双方可以声明最多愿意创建几个流ID。

流标识符

数据流通过一个无符号的62比特整数标识,也称流ID。流ID的最低2位比特用于识别流的类型(单向或双向)和流的发起者。

流ID的最低1位比特(0x1)用于识别流的发起者。客户端发起双数(最低位置0)流,服务器发起单数(最低位置1)流。

第2个比特(0x2)识别单/双向流。单向流始终置1,双向流则置0。

流并发

QUIC允许任意数量的并发流。端点通过闲置最大流ID来控制并发活动的传入流数量。

每个端点指定自己的最大流ID数,并只对对等端端点有效。

收发数据

端点使用流来收发数据,这是流的最终用途。QUIC数据流是有序的字节流抽象。但是,不同流之间是无序的。

流优先度

如果正确设置了各流的优先度,流复用机制可以显著提升应用的效率。使用其他多路复用协议(如HTTP/2)的经验表明,有效的优先度划分策略对效率具有显著的正面影响。

QUIC本身没有提供交换优先度信息的报文。接收优先度信息依赖于使用QUIC的应用层。应用层可以定义所有符合其语义的优先度方案。

基于QUIC使用HTTP/3时,优先度信息在HTTP层完成。

0-RTT

先前已连接过一个服务器的客户端可能缓存来自该连接的某些参数,并在之后与该服务器建立一个无需等待握手完成就可以立即传输信息的0-RTT连接,从而减少建立新连接所必需的时间。

旋转比特位

在QUIC工作组的设计讨论中,最长的主题之一就是旋转比特位,人们花费了数百封邮件和数百个小时来讨论它。

旋转比特位的支持者认为,两个QUIC端点之间路径上的运营商和人员需要有办法来测量延迟。

反对者则反感此功能潜在的信息泄露。

旋转一个比特

QUIC连接的客户端、服务器这两个端点各为每一个QUIC连接维护一个旋转的值——0或1,在传送时候它们在报文中设置该值。

然后,在每一次往返时,连接双方都翻转这一比特的值。效果是观察者可以检测该比特字段的0与1脉冲。

这一观测只在发送方未被应用层或流量控制限制的情况下有效,并且网络上经过重新排序的数据包也会给数据带来噪声。

用户空间实现

在用户空间中实现一个传输层协议有助于协议的快速迭代,协议的演进更为容易,不需要客户端和服务器更新其操作系统内核才能部署新的版本。

QUIC本身没有固有的东西阻碍未来在操作系统内核中实现和提供QUIC协议。

众多的实现

在用户空间中实现一个新的传输层协议时,一个显而易见的效果是我们会看到很多独立的实现。

在可预见的未来,不同的应用程序可能包含(或基于)不同的HTTP/3和QUIC实现。

API

常规TCP与程序最成功的因素之一便是标准化的套接字(socket)API。其API有着定义良好的功能,使用它能让你轻松地在各操作系统之间移植程序,因为TCP采用同样的方式运作。

但QUIC不是如此。QUIC目前没有标准化的API。

使用QUIC时,你需要选择一个现有的库实现,并坚持使用它的API。这在某种程度上把应用“绑定”到了单一的库上。换库意味着使用另外一套API,这可能带来相当的工作量。

另外,由于QUIC一般在用户空间中实现,所以它不像现有的TCP和UDP套接字API那样能轻松扩展。使用QUIC意味着选择了套接字API之外的另一套API。

前端知识体系 · wcrane