HTTP3.0为什么选择改造UDP?
在HTTP2.0中推出了许多重要的优化,如二进制分帧协议、多路复用、头部压缩、服务端推送等,大幅度的提高了HTTP的性能,将其推上了新的台阶。
但即使性能得到了优化,HTTP在某些地方还是不满足大家的预期,如:
- 建立连接的时间过长(TCP + TLS)
- 队头阻塞(TCP)
- 移动网络切换时无法保持连接(TCP)
- 网络切换时需要重新建立连接(TCP五元组标识连接)
从上面这些问题我们也可以看到,带来这些问题的竟然是我们一直所信赖的TCP协议。TCP的种种机制似乎已经不符合当前网络的发展,其连接管理、五元组标识、队头阻塞等问题大大的影响了HTTP的性能,因此,我们需要做出改变。
TCP影响性能的一些特点
那为什么最终选用了UDP呢?
首先我们需要思考,如果想对TCP进行升级,会带来多大的成本
- 基于TCP的设备、协议、应用特别多,带来的影响特别大,难以兼容(比如2013年更新的TCP Fast Open机制,至今都有大部分系统不兼容)。
- TCP协议栈是Linux内核中的重要部分,修改、升级、兼容的成本特别大。
- TCP协议复杂,改造难度高。
既然TCP走不通,此时谷歌就将目光放到了同样是传输层协议的UDP身上。此时我们来看看UDP的几个特点
- UDP无连接(没有建立连接和结束连接的成本)
- UDP数据无序,且数据报之间没有关联(无队头阻塞问题)
- UDP协议简单(修改成本低)
UDP的几个特点都完全满足我们的需求,而唯一的缺陷就是UDP无法像TCP一样具有可靠性。因此谷歌决定中UDP的基础上改造出一个具备TCP优点的新协议——QUIC协议。
QUIC协议
HTTP3.0与QUIC协议
QUIC ,即快速UDP网络连接 (Quick UDP Internet Connections ), 是由谷歌提出的实验性网络传输协议 ,位于OSI模型的传输层。 QUIC旨在解决TCP协议的缺陷,并最终替代TCP协议, 以减少数据传输,降低连接建立延迟时间,加快网页传输速度。
其主要特点如下
QUIC的特点
多路复用
队头阻塞其实是计算机网络中一个很常见的问题,它的主要表现就是一个数据包影响了一堆数据包,如果它没有到来就会一直阻塞影,从而影响其他的数据包。这个问题在HTTP1.x和TCP中都会出现。
在HTTP1.1引入管线化机制时,由于其通过队列来管理请求,如果响应的顺序与请求不一致,则此时就会阻塞在队首。为了解决这个问题,在HTTP2.0中引入了多路复用机制,通过在头部中标识了对应的请求信息,解决了队首阻塞问题,提高了信道的利用率。
管线化对比多路复用
虽然HTTP解决了队头阻塞问题,但是TCP层仍然存在队头阻塞问题。TCP协议为了确保将数据有序交付给上层,其需要等待所有数据到来并排序整合,一旦某个包丢失,就必须等待其重传,从而阻塞整个连接的数据使用。
为了解决这个问题,QUIC采用了多路复用的思想,一个连接上可以同时承载多个流,可以同时发起多个请求,同时这些请求之间完全独立,某个请求的阻塞不会影响到其他的请求。
低等待延迟(0RTT)
RTT(Round-Trip Time)即数据包一来一回的时间消耗,是衡量网络建立连接的常用指标,其包含三部分(往返传播时延、网络设备内排队时延、应用程序数据处理时延)。
对于传统的HTTPS协议来说,其建立连接的过程如下:
- 执行三次握手,建立TCP连接。
- 执行TLS握手,建立TLS连接。
TCP+TLS建立连接
从上图可以看出,客户端与服务器之间需要进行多轮的协议交互来完成握手与加密协商(3RTT),才能进行数据的传输。这也就导致了连接的延迟特别的严重,访问HTTPS网站时明显就比HTTP慢得多。
那我们有没有方法降低延迟呢?
答案是肯定的,从上图可以看出,降低延迟最简单的一个方法其实就是减少通信的次数,那我们能否将TCP三次握手和TLS合并起来呢?这其实就是QUIC降低延迟的思路。
QUIC可以达成0RTT建立连接,第一个数据包就可以传输数据。
QUIC与传统TCP+TLS对比
那QUIC是如何实现的0RTT建连的呢?下面就来讲讲它的原理。
连接方式
QUIC的0RTT并不是无条件的,因为对于第一次交互的双方来说,还需要使用1RTT进行密钥协商(DH算法)。因此QUIC的建连主要分为首次连接和非首次连接两种情况。
首次连接
其主要内容就是客户端和服务端的密钥协商和数据传输。基本流程如下:
- 客户端对于首次连接的服务端先发送
client hello 请求。 - 服务端生成一个素数p和一个整数g,同时生成一个随机数为私钥,然后计算出公钥。服务端将公钥,p,g三个元素打包成为
config ,后续发送给客户端。 - 客户端随机生成一个自己的私钥,再从
config 中读取g和p,计算出自己的公钥。 - 客户端使用自己的私钥和服务端发来的
config 中读取的服务端公钥,生成后续数据加密用的密钥K。 - 客户端使用密钥K加密业务数据,并追加自己的公钥,都传递给服务端。
- 服务端根据自己的私钥和客户端公钥生成客户端加密用的密钥K。
- 为了保证数据安全,上述生成的密钥K只会生成使用1次,后续服务端会按照相同的规则生成一套全新的公钥和私钥,并使用这组公私钥生成新的密钥M。
- 服务端将新公钥和新密钥M加密的数据发给客户端,客户端根据新的服务端公钥和自己原来的私钥计算出本次的密钥M,进行解密。
- 之后的客户端和服务端数据交互都使用密钥M来完成,密钥K只使用1次。
首次连接流程
非首次连接
在首次连接中客户端存储了服务端的config (服务端公钥、随机素数p、随机整数g),因此在后续的连接中可以直接使用config计算出通信的密钥,从而跳过了密钥协商的1RTT,实现了0RTT的数据交互。
为了确保安全,客户端保存config 是有时间期限的,因此在config 失效之后仍然需要进行首次连接的密钥交换。
前向安全
前向安全(Forward Secrecy),是密码学中通讯协议的安全属性,指的是长期使用的主密钥泄漏不会导致过去的会话密钥泄漏。 前向安全能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。如果系统具有前向安全性,就可以保证在主密钥泄露时历史通讯的安全,即使系统遭到主动攻击也是如此。
简单来说,前向安全指的是密钥泄漏也不会让之前加密的数据被泄漏,影响的只有当前,对之前的数据无影响。
从上面DH加密的流程中我们可以看到,为了保证数据安全,每次通信时使用的密钥只会使用一次,每次交互完都会将其销毁,后续再按照相同的规则重新生成。即使密钥泄漏出去,对方也只能获取当对应密钥的那条信息,而对其他的数据不会有任何影响。
前向安全问题简图
前向纠错
前向纠错是一种差错控制方式,它是指信号在被送入传输信道之前预先按一定的算法进行编码处理,加入带有信号本身特征的冗码,在接收端按照相应算法对接收到的信号进行解码,从而找出在传输过程中产生的错误码并将其纠正的技术
当出现丢包时,TCP采用的是重传机制,而QUIC采用的是前向纠错机制。
- 对于TCP来说,如果发生丢包,则需要一个等待延时判断是否发生了丢包,然后再启动重传机制,这个过程会造成一定的阻塞,影响传输的时间。
- QUIC每发送一组数据就对这组数据进行异或运算,并将结果作为一个校验和包发送出去。如果前面这组数据中出现了丢包的情况,就可以通过校验和与其他包来还原出丢包的数据,避免了重传带来的损耗。
连接迁移
在当前的移动网络环境下,用户的网络随时都有可能发生切换,比如从移动网络切换到WIFI环境,此时我们的IP地址就会发生变化。由于TCP协议使用五元组(源IP,源端口,目的IP,目的端口,传输层协议) 来唯一表示一条连接,因此之前的连接就不可能继续保持,就需要重新建立TCP连接。
为了解决这个问题,基于UDP实现的QUIC完全摒弃了五元组的概念,其通过生成一个64位的随机数作为连接的标识,即使当我们发生了IP地址的切换,这个标识也不会发生任何变化,我们就可以快速的恢复连接,大大的改善了移动端应用的体验。
|