初识 QUIC 协议
QUIC 概述
QUIC 全称 quick udp internet connection,快速 UDP 互联网连接(和英文 quick 谐音,简称“快”),是使用 UDP 进行多路并发传输的协议。QUIC 协议最初是由 Google 开发并使用在 Chrome 中的新一代 Web 协议。之后被 IETF 的 QUICWG 工作组接管,目前发布的版本为 24th。
QUIC 协议是一个包含传输层、安全垫片和应用层的复合协议族。QUIC 协议基于 UDP 协议,在此之上实现了可控有序到达、拥塞控制、流量控制等传输层功能; 提供了对于包和连接的完整性和机密性功能;提供了 http3.0 等的应用层功能。
随着移动互联网快速发展以及物联网的逐步兴起,网络交互的场景越来越丰富,网络传输的内容也越来越庞大,用户对网络传输效率和 Web 响应速度的要求也越来越高。
与现在广泛应用的 HTTP2+TCP+TLS 协议组合相比,QUIC 具有如下优势:
- 减少了 TCP 三次握手及 TLS 握手时间;
- 改进的拥塞控制;
- 避免队头阻塞的多路复用;
- 连接迁移;
- 前向冗余纠错
QUIC 协议基本结构
QUIC 协议是基于 UDP 的协议,其最基本包的格式为 UDP 报文(package),由 package number 来提供有序到达的功能。 在一个 QUIC 报文之中,有多个独立功能的 QUIC 帧(frame),每一个帧都 用于实现不同的功能,比如传输帧,错误帧,校验帧等。
在报文和帧的基础之上,QUIC 协议构成了一个双方共享连接状态的有状态协议, 因此有连接(connection)的概念。通过 connection id 来区别不同的 QUIC 连接。 QUIC 连接具有完整的生命周期(建立、撤销、关闭等)。
在连接之上,QUIC 协议使用了连接复用的概率,在连接之上建立了多个有状态的流(steam), 每一个流也是具有独立状态的。每一个流对应了上层的不同应用,比如 id 为 1 的流用于负责握手协议和密码学参数的协商。
+---------+---------+---------+
| stream1 | stream2 | stream3 |
+---------+---------+---------+
| connection |
+-----------------------------+
| packages (frame1 || frame2 )|
+-----------------------------+
QUIC 核心特性
减去不必要的RTT,连接建立延时低
0RTT 建连可以说是 QUIC 相比 HTTP2 最大的性能优势。那什么是 0RTT 建连呢?这里面有两层含义:
- 传输层 0RTT 就能建立连接;
- 加密层 0RTT 就能建立加密连接;
比如上图左边是 HTTPS 的一次完全握手的建连过程,需要 3 个 RTT。就算是 Session Resumption,也需要至少 2 个 RTT。
而 QUIC 呢?由于建立在 UDP 的基础上,同时又实现了 0RTT 的安全握手,所以在大部分情况下,只需要 0 个 RTT 就能实现数据发送,在实现前向加密的基础上,并且 0RTT 的成功率相比 TLS 的 Sesison Ticket 要高很多。
改进的拥塞控制
TCP 的拥塞控制实际上包含了四个算法:慢启动、拥塞避免、快速重传、快速恢复。其中TCP中拥塞控制是被编译进内核中的,如果想要更改就需要改变内核参数,但是想要对已有的拥塞控制算法进行更改就需要重新编译内核,Linux 4.9 中引入了基于时延的拥塞控制算法 BBR,这打破了以往是靠丢包驱动的拥塞控制算法。
QUIC 协议当前默认使用了 TCP 协议的 Cubic 拥塞控制算法,同时也支持 CubicBytes、Reno、RenoBytes、BBR、PCC 等拥塞控制算法。与 TCP 相比,QUIC 主要拥有以下特性:
- 可插拔:QUIC 可以针对某个特殊场景使用不同的拥塞控制算法,且切换十分简单。TCP 想要切换拥塞控制算法是针对全局的,需要更改内核参数,或者重新编译内核,使得切换起来十分不便;
- 单调递增的 Packet Number:QUIC 的数据包是单调递增的Packet Number。这帮助传输解决了重传歧义,方便计算RTO,解决了TCP的队头阻塞。因为TCP的数据是流,确认机制是 seq和ack。QUIC使用 Packet Number 代替了 TCP 的 sequence number,并且每个 Packet Number 都严格递增,也就是说就算 Packet N 丢失了,重传的 Packet N 的 Packet Number 已经不是 N,而是一个比 N 大的值。
QUIC 协议测试实践
安装 go 环境,并且下载 quic-go 项目
# 配置 go 安装环境,下载 quic-go 项目源码
go env -w GO111MODULE=on
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
go get -v -t -u github.com/lucas-clemente/quic-go
(以下测试实践基于开源项目 quic-go https://github.com/lucas-clemente/quic-go)
测试 QUIC 的连接时长
编译项目的测试代码
# 编译 quic-go 项目
cd ./quic-go/example
go build -o server main.go
go build -o client client/main.go
示例中采用了单向认证的方式,证书签发的 CN=localhost。
开启服务端
./server -bind ":8088"
server GET localhost:8088/demo/tiles
server Responding with 200
server Peer closed session with error: Application error 0x100
server Connection 0xfc314d4e5df5d38b closed.
开启客户端
./client https://localhost:8088/demo/tiles
GET https://localhost:8088/demo/tiles
client Starting new connection to localhost ([::]:52586 -> 127.0.0.1:8088), source connection ID (empty), destination connection ID 0xfc314d4e5df5d38b, version TLS dev version (WIP)
Got response for https://localhost:8088/demo/tiles: &http.Response{Status:"200 OK", StatusCode:200, Proto:"HTTP/3", ProtoMajor:3, ProtoMinor:0, Header:http.Header{}, Body:(*http3.body)(0xc000324a00), ContentLength:0, TransferEncoding:[]string(nil), Close:false, Uncompressed:false, Trailer:http.Header(nil), Request:(*http.Request)(nil), TLS:(*tls.ConnectionState)(nil)}
Request Body:
<html><head><style>img{width:40px;height:40px;}</style></head><body><img src="/demo/tile?cachebust=0">...<img src="/demo/tile?cachebust=199"></body></html>
client Closing session with error: Application error 0x100
client Connection 0xfc314d4e5df5d38b closed.
运行结果:QUIC 单向认证
通过对比,可以得出结论:同等条件下 QUIC 比 HTTP2 传输的数据包少,时间更短。但在数据量较小时两者相差不大,当大数据量、多次连接时 QUIC 更能体现出优势。简单测试单次的链接时间结果:连接→发送→接收→断开:
grpc_simple: 28ms
grpc_single: 44ms
grpc_dul: 51ms
http_simple: 21ms
http_single: 32ms
http_dual: 38ms
quic_single: 35ms
测试 QUIC 在 Web 上的服务
首先需要去网上找到一个可以提供 QUIC 服务的网站,然后再使用上面的 client 去请求。如果正常,则可以成功获取响应:
# 这里使用大佬的技术博客(已提供 QUIC 服务)作为请求地址
./client [https://liudanking.com](https://liudanking.com/)
运行后的部分截图:
获取响应后,可以在 Chrome 浏览器(版本号 64+)中开启网络状态数据看板,并且请求上面的同一个地址,即可获取到如下的 QUIC 交互截图:
结论
QUIC 协议开创性的使用了 UDP 协议作为底层传输协议,通过各种方式减少了网络延迟。
目前 QUIC 协议已经运行在一些较大的网站上,但现在还在标准化进程中,离大范围普及还有较长的一段距离。期待 QUIC 协议规范能够成为终稿,并在除了谷歌浏览器之外的其他浏览器和应用服务器中也能够实现。