QUIC
QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低时延的互联网传输层协议。我们知道,TCP/IP协议族是互联网的基础。其中传输层协议包括TCP和UDP协议。与TCP协议相比,UDP更为轻量,但是错误校验也要少得多。这意味着UDP往往效率更高(不经常跟服务器端通信查看数据包是否送达或者按序),但是可靠性比不上TCP。通常游戏、流媒体以及VoIP等应用均采用UDP,而网页、邮件、远程登录等大部分的应用均采用TCP。
QUIC很好地解决了当今传输层和应用层面临的各种需求,包括处理更多的连接,安全性,和低延迟。QUIC融合了包括TCP,TLS,HTTP/2等协议的特性,但基于UDP传输。QUIC的一个主要目标就是减少连接延迟,当客户端第一次连接服务器时,QUIC只需要1RTT(Round-Trip Time)的延迟就可以建立可靠安全的连接,相对于TCP+TLS的1-3次RTT要更加快捷。之后客户端可以在本地缓存加密的认证信息,再次与服务器建立连接时可以实现0-RTT的连接建立延迟。QUIC同时复用了HTTP/2协议的多路复用功能(Multiplexing),但由于QUIC基于UDP所以避免了HTTP/2的队头阻塞(Head-of-Line Blocking)问题。因为QUIC基于UDP,运行在用户域而不是系统内核,使得QUIC协议可以快速的更新和部署,从而很好地解决了TCP协议部署及更新的困难 。
TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的。
4、UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。
5、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节 。
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
低连接延迟
传统的TCP三次握手需要2个RTT
QUIC 的握手是基于 TLS1.3 实现的,所以首次建立连接时也是需要 1 次 RTT,之后的连接可以做到0-RTT,那么QUIC 是如何做到 0-RTT 握手的呢?
其实原理很简单:客户端缓存了 ServerConfig(B=b*G%P),下次建连直接使用缓存数据计算通信密钥,也就是说,客户端不需要经过握手就可以发送应用数据,这就是 0-RTT 握手。
可靠传输
QUIC 是基于 UDP 协议的,而 UDP 是不可靠传输协议,那 QUIC 是如何实现可靠传输的呢? 可靠传输有 2 个重要特点:
- 完整性:发送端发出的数据包,接收端都能收到
- 有序性:接收端能按序组装数据包,解码得到有效的数据
问题 1:发送端怎么知道发出的包是否被接收端收到了?通过包号(PKN)和确认应答(SACK)。
- 客户端:发送 3 个数据包给服务器(PKN = 1,2,3)。
- 服务器:通过 SACK 告知客户端已经收到了 1 和 3,没有收到 2。
- 客户端:重传第 2 个数据包(PKN = 4)。
由此可以看出,QUIC 的数据包号是单调递增的。也就是说,之前发送的数据包(PKN=2)和重传的数据包(PKN=4),虽然数据一样,但包号不同。
Packet Number 单调递增的两个好处:
- 可以更加精确计算 RTT,没有 TCP 重传的歧义性问题;
- 可以支持乱序确认,防止因为丢包重传将当前窗口阻塞在原地,而 TCP 必须是顺序确认的,丢包时会导致窗口不滑动;
问题 2:既然包号是单调递增的,那接收端怎么保证数据的有序性呢?通过数据偏移量 offset。
每个数据包都有一个 offset 字段,表示在整个数据中的偏移量。接收端根据 offset 字段就可以对异步到达的数据包进行排序了。
问题 3:为什么 QUIC 要将 PKN 设计为单调递增?解决 TCP 的重传歧义问题。
由于原始包和重传包的序列号是一样的,客户端不知道服务器返回的 ACK 包到底是原始包的,还是重传包的。但 QUIC 的原始包和重传包的序列号是不同的,也就可以判断 ACK 包的归属。
流量控制
和 TCP 一样,QUIC 也是利用滑动窗口机制实现流量控制。 在TCP头中有一个Window字段,这个字段代表了接收端告诉发送端自己缓冲区还有多少剩余空间可以接收数据。TCP 利用滑动窗口实现流量控制的机制, 而滑动窗口大小就是通过TCP头部的Window字段来通知发送方。
接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去。而发送方根据ACK报文里的窗口大小的值进而改变自己的发送速度。
假设我们窗口大小为20(32-51),发送方发送32-51序列包后,接收到ACK=36,表示 接收方只接收到了32-35,下一次接收方期望接收到的是36序列号的包。如果接收方给到的ACK中窗口大小仍为20,发送方窗口滑动,36-55则是发送方可以发送的包。
这就是滑动窗口的工作机制,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;发送方收到ACK 计算获得接收方窗口大小之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小实现流量控制。但是当发送方收到接收窗口的大小为0时,发送方就会停止发送数据!
拥塞控制
流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。 拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况。
拥塞控制就是防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。 拥塞是一个动态问题,我们没有办法用一个静态方案去解决,从这个意义上来说,拥塞是不可避免的。就好像上下班高峰期经常堵车,为了不让交通瘫痪,交警会去现场指挥,采用动态的方式对车辆进行限制,根据实际情况,慢慢放行。
多路复用
HTTP/2 虽然通过多路复用解决了 HTTP 层的队头阻塞,但仍然存在 TCP 层的队头阻塞。那 QUIC 是如何解决 TCP 层的队头阻塞问题的呢?其实很简单,HTTP/2 之所以存在 TCP 层的队头阻塞,是因为所有请求流都共享一个滑动窗口,那如果给每个请求流都分配一个独立的滑动窗口,是不是就可以解决这个问题了?
连接迁移
移动设备接入的网络会频繁变动,从而导致设备IP地址改变。对于通过四元组(源IP、源端口、目的IP、目的端口)定位连接的TCP协议来说,这意味着连接需要断开重连,所以上述2个RTT的建链时延、TCP慢启动都需要重新来过。而HTTP3的QUIC层实现了连接迁移功能,允许移动设备更换IP地址后,只要仍保有上下文信息(比如连接ID、TLS密钥等),就可以复用原连接。 QUIC 能实现连接迁移的根本原因是底层使用 UDP 协议就是面向无连接的。
HTTP3
HTTP2和HTTP3的传输层是完全不同的协议,HTTP3的传输层是UDP协议。我们知道UDP协议是个不可靠的协议,而TCP协议是可靠协议,怎样保证可靠的呢,重传。
http2遗留的问题:
HTTP2协议虽然大幅提升了HTTP/1.1的性能,然而,基于TCP实现的HTTP2遗留下3个问题:
对队头阻塞
有序字节流引出的队头阻塞,使得HTTP2的多路复用能力大打折扣。
建立连接慢
TCP与TLS叠加了握手时延,建立链接的时长还有1倍的下降空间。
不支持连接迁移
基于TCP四元组确定一个连接,这种诞生于有线网络的设计,并不适合移动状态下的无线网络,这意味着IP地址的频繁变动会导致TCP连接、TLS会话反复握手,成本高昂。
HTTP3协议解决了这些问题:
更快的连接时延
HTTP3重新定义了TLS协议加密QUIC头部的方式,既提高了网络攻击成本,又降低了建立连接的速度(仅需1个RTT就可以同时完成建链与密钥协商);
没有tcp的队头阻塞
HTTP3基于UDP协议重新定义了连接,在QUIC层实现了无序、并发字节流的传输,解决了队头阻塞问题。
没有使用tcp,自然也不会有tcp的队头阻塞,但是如何保证数据有序到达呢?
http3使用http2中流的思想,在应用层和传输层加了个quic协议层。
加密的报文头部
tcp使用tls虽然能加密报文数据,但是tcp头部没有加密。所以在传输过程中很容易被中间网络设备篡改,注入和窃听。比如修改序列号、滑动窗口。quic 的 packet 可以说是武装到了牙齿。除了个别报文外所有报文头部都是经过认证的,报文 Body 都是经过加密的。
连接迁移
HTTP3 将Packet、QUIC Frame、HTTP3 Frame分离,实现了连接迁移功能,降低了5G环境下高速移动设备的连接维护成本。