QUIC协议的简要介绍

patoritu 2022-01-15 23:22:50

一、QUIC协议的简要概述

为什么需要QUIC协议?

基于TCP传输存在的两个重要的问题:

1):建立连接的握手延迟大

数据传输前TCP先要进行"三次握手",建立连接后才开始传输应用数据,这无疑是增加了网络延时;在采用TLS协议时需要交换密钥,又增加了一次往返时延RTT。

2):队头阻塞问题

TCP保证有序传输。当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面的排队的所有请求也一同被阻塞了。这样会导致客户端一直请求不到数据,这也就是“队头阻塞”,好比上班的路上塞车。

QUIC是一种基于UDP作为底层传输的协议,通过采用UDP为传输层协议,避免建立连接的握手延迟大的问题和队头阻塞问题,并且通过一种巧妙的机制保证传输的可靠性传输,克服UDP传输存在的问题。

二、QUIC协议的层次

QUIC协议内置了TLS栈,可以实现自己的传输加密,同时QUIC还包含了部分的HTTP/2的实现,底层通过UDP协议代替了TCP,上层只需要一层用于和远程服务器交互的HTTP/2 API。因为QUIC协议中已经包含了多路复用连接管理,HTTP API只需要完成HTTP协议的解析即可。

                                                   协议层次对比图

可以用一个公式表示:

TCP + TLS + HTTP2 = UDP + QUIC + HTTP2 API

 三、QUIC的实现原理

 

preview

 

一个 QUIC 数据包由 header 和 data 两部分组成。

header 是明文的,包含 4 个字段:Flags、Connection ID、QUIC Version、Packet Number;

data 是加密的,可以包含 1 个或多个 frame,每个 frame 又分为 type 和 payload,其中 payload 就是应用数据;

数据帧有很多类型:Stream、ACK、Padding、Window_Update、Blocked 等。

3.1 0-RTT 握手

0-RTT就是客户端与服务端通信连接时,第一个数据包便可以携带有效数据。

QUIC协议依赖于合并加密和数据传输过程中的握手来创建一个安全的连接。 在连接建立成功后, 客户端会缓存起来原始的连接信息等。 在接下来与相同的服务器建立连接的过程中, 客户端能够在不增加额外RTT的情况下建立一个加密的连接,数据要发送的数据可以在握手的包中捎带着发送过去,而不用等待服务器的回复,从而实现0RTT。

所以,所谓QUIC的0RTT是指在建立连接之后,后续发送数据都不需要增加额外的RTT时间,最开始的握手还是需要1RTT的时间消耗的

3.2 向前纠错

QUIC 协 议 有 一 个 非 常 独 特 的 特 性,称 为 向 前 纠 错(Forward Error Correction,FEC),每个数据包除了它本身的内容之外,还包括了部分其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。向前纠错牺牲了每个数据包可以发送数据的上限,但是减少了因为丢包导致的数据重传,因为数据重传将会消耗更多的时间(包括确认数据包丢失、请求重传、等待新数据包等步骤的时间消耗。

3.3可靠传输

QUIC 是基于 UDP 协议的,而 UDP 是不可靠传输协议,那 QUIC 是如何实现可靠传输的呢?

可靠传输有 2 个重要特点:

(1)完整性:发送端发出的数据包,接收端都能收到

问题 1:发送端怎么知道发出的包是否被接收端收到了?

解决方案:通过包号(PKN )和确认应答(SACK),发送端可以确认发出的包是否被接收到。如果发生了丢包,则重传。QUIC 的数据包号是单调递增的。也就是说,之前发送的数据包和重传的数据包,虽然数据一样,但包号不同。

(2)有序性:接收端能按序组装数据包,解码得到有效的数据

问题 2:接收端怎么保证数据的有序性呢?

解决方案:通过数据偏移量offset。每个数据包都有一个offset字段,表示在整个数据中的偏移量。接收端根据offset字段就可以对异步到达的数据包进行排序了。

3.4 流量控制

​和TCP一样,QUIC也是利用滑动窗口机制实现流量控制:

preview
发送端的窗口大小由接收端告知,包括发送窗口和可用窗口,如果发送端口收到了接收端口的ACK确认应答,那么整个窗口就会向右滑动,发送新的数据包。

和TCP不同的是,QUIC的滑动窗口分为Connection和Stream两种级别。Connection流量控制规定了所有数据流的总窗口大小,Stream流量控制规定:每个流的窗口大小。

3.4 多路复用

多路复用是HTTP/2的主要特性之一。QUIC协议中包含了多路复用。
概念:单条TCP连接上可以同时发送多个HTTP请求

为什么HTTP/2可以实现多路复用?
简单回答就是因为HTTP/2采用了二进制帧格式的数据结构。

 

Length(3Byte)Type(1Byte)Flags(1Byte)Stream ID (4Byte)Payload(应用数据)
Length表示 Payload 的长度
Type表示帧类型
Flags帧标识
Stream ID数据帧所属的流
Payload应用数据,长度由 Length 字段指定

由于HTTP/2是分帧的,请求和响应可以交错甚至可以复用。可参考QUIC协议数据包格式的DATA(密文部分)

3.5 连接迁移

连接迁移:当客户端切换网络时,和服务器的连接并不会断开,仍然可以正常通信,对于 TCP 协议而言,这是不可能做到的。因为 TCP 的连接基于 4 元组:源 IP、源端口、目的 IP、目的端口,只要其中 1 个发生变化,就需要重新建立连接。但 QUIC 的连接是基于 64 位的 Connection ID,网络切换并不会影响 Connection ID 的变化,连接在逻辑上仍然是通的。可参考QUIC协议数据包格式的Header(明文部分)

三、QUIC协议的配置使用指南

基于linux的quick-go环境搭建

1.从https://golang.org/dl/下载golang编译器,要求go版本为1.14+。

2.下载quic-go并修改quic-go的文件权限

git clone https://github.com/lucas-clemente/quic-go.git
chmod 777 quic-go

 3.编译

服务端

cd quic-go 
cd example
go build main.go
之后使用./main -qlog -v -tcp运行即可。

必须带上-tcp参数是因为浏览器第一次访问时仍然是要通过TCP进行的,如果不带浏览器将无法访问。

客户端

先修改example/client/main.go,在60行之后加上qconf.Versions = []protocol.VersionNumber{protocol.VersionDraft29},选择quic版本为draft-29。

cd example/client
go build main.go

之后使用./main -v -insecure -keylog ssl.log https://quic.rocks:4433/即可访问支持quic协议的网站。

 

四、QUIC协议源码分析

在example的client代码中,通过http3.RoundTripper建立了一个中间件,之后将roundTripper传递给http.Client建立了一个http客户端,并以此来发起http请求。

roundTripper := &http3.RoundTripper{
    TLSClientConfig: &tls.Config{
        RootCAs:            pool,
        InsecureSkipVerify: *insecure,
        KeyLogWriter:       keyLog,
    },
    QuicConfig: &qconf,
}
defer roundTripper.Close()
hclient := &http.Client{
    Transport: roundTripper,
}
rsp, err := hclient.Get(addr)
http3.RoundTripper实现了net.RoundTripper接口,使http客户端将发起请求的过程交由该中间件来处理。该接口定义如下,只有一个函数RoundTrip接受一个http请求,返回http响应。
type RoundTripper interface { 
       RoundTrip(*Request) (*Response, error)
}

http3.RoundTripper的实现中,将请求又交给了RoundTripOpt函数来处理。该函数中首先判断请求是否合法,如果不合法就关闭请求,合法就会通过cl, err := r.getClient(hostname, opt.OnlyCachedConn)来获取quic客户端。

而在getClient函数中,通过hash表来获取quic client,如果不存在就会通过newClient函数建立新client。

当获取到client之后,就会通过client.RoundTrip函数发起请求。

而在client.RoundTrip中,在发起请求之前,会调用authorityAddr来确保源地址不是伪造的。当第一次发送请求时会调用dial函数进行握手,如果使用0rtt请求,就立即发送请求,否在当握手完成后通过doRequest发出请求。

五、参考资料

1.《QUIC协议研究》作者:陈振波 武汉邮电科学研究院

2.QUIC 协议详解 链接

3.QUIC协议分析-基于quic-go 链接

作者:NP537

...全文
839 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧