什么是QUIC
QUIC,快速UDP网络连接(Quick UDP Internet Connection)的简称,即RFC文档描述它为一个面向连接的安全通用传输协议。其基于UDP协议实现了可靠传输及拥塞控制,简单来说,QUIC = TCP + TLS。
为什么有了QUIC
HTTP2.0为了为了解决HTTP1.x请求队头阻塞的问题(收到前一个请求的响应才允许发送下一个请求),抽象出了stream的概念。在一条HTTP连接中支持多条stream,每条stream可以单独发送请求而与其它stream互不干扰,这样就可以同时发生多个请求,而不必非要等到收到响应,提高了发送效率。
但是,虽然应用层不存在队头阻塞的问题了,由于HTTP底层是TCP协议,多个stream仍然是复用一条TCP连接,所以当多个请求到达服务端,接收队列可能是这个样子的:
看到了吧,虽然请求二的的包都已经完整,但由于前面有一个5号包迟迟没有到来,导致这个请求被阻塞,这就是TCP队头阻塞问题。QUIC设计出来的目的这就人尽皆知了!
协议栈
谁都知道TCP在传输层,HTTP在应用层,TLS在之间的安全套接层,那么QUIC在哪一层呢?
ヽ(゚Д゚)ノ 不好意思,上错图了,是这张才对:
QUIC在UDP之上,属于应用层,HTTP3基于QUIC实现。
QUIC特性
快速握手
众所周知TCP需要三次握手,对于开启了TCP Fast Open选项的TCP连接来说,建立可靠连接需要1个RTT。但安全连接的建立TLS握手一般需要2个RTT,如此总共就是3个RTT才能建立安全、可靠的连接。那么QUIC呢?
1-RTT
- 客户端发送一个包含QUIC版本号、客户端传输参数、加密扩展(包括TLS 1.3的Client Hello消息)的QUIC初始包(Initial packet)。这个包使用QUIC特定的初始密钥进行加密,这些密钥是从客户端的连接ID和QUIC版本号派生的。Client Hello中包括TLS协议版本、支持的密码套件、压缩方法、扩展、客户端随机数等信息。
- 服务器收到客户端的初始包后,发送一个包含自己的传输参数和加密扩展(包括TLS 1.3的Server Hello消息)的QUIC初始包以及Handshake packet。Server Hello中包括服务器选择的密码套件、服务器随机数、服务器证书、证书验证和加密扩展。Handshake packet,其中包含加密的扩展和TLS握手的其余部分,如服务器的Finished消息。
- 客户端收到服务器的初始包和Handshake packet后,验证服务器证书和Finished消息。然后,客户端发送包含TLS 1.3的Client Finished消息的Handshake packet,完成TLS握手。此时,客户端和服务器都有了足够的信息来生成最终的加密密钥,用于保护QUIC通信。
一旦TLS握手完成,客户端和服务器就可以开始使用1-RTT密钥来加密和解密数据包,进行安全的数据传输。
0-RTT
在先前的成功的QUIC连接中,客户端和服务器通过完整的1-RTT握手建立了一个安全的会话,并且客户端和服务器互相缓存了对端的协商参数,这个参数包含了用于将来0-RTT握手的必要信息。
- 当客户端希望重新连接到服务器时,它使用保存的服务器参数计算出前一个1-RTT阶段的会话密钥,用这个密钥加密数据发送。同时,开启一个1-RTT的协商流程。
- 服务器接收到客户端的0-RTT数据后,使用存储的会话信息来解密和处理这些数据。同时,也会响应客户端的1-RTT握手。
新的握手流程跟初始的1-RTT握手没什么两样,只是已经开始携带数据了。新的握手完成后,会话密钥切换为新的,保证前向安全。
连接迁移
考虑到现在普通使用移动互联,一会在家连个wifi,一会出门自动换到4G或者5G,进个奶茶店又连上了WIFI。如果使用TCP连接,那么会连接多次断开重连,造成不好的用户体验。因此,QUIC支持了连接迁移的功能。
传统的 TCP 协议是以四元组(源 IP 地址、源端口号、目的 ID 地址、目的端口号)来标识一条连接,那么一旦四元组的任何一个元素发生了改变,这条连接就会断掉,那么这条连接中正在传输的数据就会断掉,切换到新的网络后可能需要重新去建立连接,然后重新发送数据。这将会导致用户的网络会“卡”一下。
新时代的QUIC 不再以四元组作为唯一标识,转而使用连接 ID 来标识一条连接,这样无论你的网络如何切换,底层的IP端口如何变化,QUIC依然是稳如老狗!
无队头阻塞
正如开篇所说,HTTP2.0由于底层使用TCP连接,所以存在传输层的队头阻塞问题,导致后到的同个连接里的请求无法及时处理,基于UDP的QUIC自然不存在这个问题了,实际实现中,QUIC中的每个stream都会维护一个自身的接收窗口,请求组包完整后可以立即处理,跟其它请求彻底撇清关系!
包序号递增
TCP为了保证可靠性,使用了基于字节序号的Seq及Ack来确认消息的有序到达。
QUIC同样是一个可靠的协议,不同的是,它使用Packet Number代替了TCP的Seq,并且每个Packet Number 都严格递增,也就是说就算Packet N 丢失了,重传的Packet N的Packet Number已经不是N,而是一个比N 大的值。而TCP呢,重传包的Seq和原始的包的Seq是一样的,也正是由于这个特性,导致TCP重传存在歧义。具体的,可能影响RTT等参数的计算,导致拥塞避免算法不够准确。
具体的,QUIC通过Stream ID及Offset 来确定重传的数据包位于原始数据包的哪个位置。
可定制化的拥塞控制算法
不同于TCP实现在内核层,跟内核深度绑定,想要修改协议复杂度可想而知,且现网设备也不是那么容易替换的,存在很大的成本问题。QUIC使用可插拔的拥塞控制,相较于TCP,它能提供更丰富的拥塞控制信息。QUIC带有收到数据包与发出ACK之间的时延信息。这些信息能够帮助更精确的计算 RTT。另外,QUIC ACK包的 Frame 支持256个NACK 区间,相比于TCP的SACK(Selective Acknowledgment)更精细,可以让client和server更清楚的确认哪些包已经被对方收到。
QUIC 的传输控制不再依赖内核的拥塞控制算法,而是实现在应用层上,这意味着我们根据不同的业务场景,实现和配置不同的拥塞控制算法以及参数,甚至是每个 Stream 都可以实现不同的拥塞控制算法。
总结
QUIC的改进之处还不止这些,更多的细节还需要深入的了解。QUIC 协议的出现,简直为HTTP3铺平了道路!冲浪速度又要进一步提升啦!