最近关于HTTP/3的新闻层出不穷,越来越多的国际大公司开始使用HTTP/3。因此,HTTP/3已经在路上了,被充分利用只是时间问题。那么,作为一线开发者,我们是时候了解什么是HTTP/3,为什么需要HTTP/3了。那么,本文将解释什么是HTTP/3?它使用什么技术?解决了哪些问题?HTTP/2的问题在写这篇文章之前,我写了一篇文章《HTTP/2做错了什么?刚刚辉煌2年就要被弃用了?》分析HTTP/2的问题及其背后的原因。这里就不详细介绍了。强烈建议您先阅读本文,对您学习本文有帮助。在上一篇文章中,我们提到HTTP/2存在TCP队头阻塞、TCP握手延迟长、协议僵化等问题,因为底层传输层协议仍然是TCP。因此,虽然HTTP/2使用了多路复用和二进制分帧等技术,但仍有优化空间。QUIC协议我们知道,HTTP/2之所以被“废弃”,是因为它使用的传输层协议仍然是TCP,所以HTTP/3解决的第一个问题就是绕过TCP。所以如果开发出新的协议,还是会受到中间设备刚性的影响,无法大规模应用。因此,开发人员想到了一种基于UDP的方式。因此,谷歌率先采用了这种方法并付诸实践。他们在2013年推出了一个名为QUIC的协议,全称是QuickUDPInternetConnections。从名字就可以看出,这是一个完全基于UDP的协议。在设计之初,谷歌希望用这个协议来代替HTTPS/HTTP协议来加速网页的传输。2015年6月,QUIC网络草案正式提交给互联网工程任务组。2018年10月,互联网工程任务组的HTTP和QUIC工作组正式将基于QUIC协议的HTTP(英文:HTTPoverQUIC)更名为HTTP/3。所以我们现在说的HTTP/3其实就是HTTPoverQUIC,也就是基于QUIC协议的HTTP。所以,想要了解HTTP/3的原理,只需要了解QUIC就可以了。QUIC协议具有以下特点:基于UDP的传输层协议:它使用UDP端口号来标识特定机器上的特定服务器。可靠性:虽然UDP是一种不可靠的传输协议,但是QUIC在UDP的基础上做了一些修改,从而提供了类似TCP的可靠性。它提供数据包重传、拥塞控制、传输步调和TCP中的其他功能。实现字节流无序并发:QUIC的单个数据流可以保证有序传递,但是多个数据流可能是无序的,也就是说单个数据流的传输是有序的,但是多个数据流的顺序接收收件人发送的订单可能与发件人发送的订单不同!快速握手:QUIC提供0-RTT和1-RTT连接建立使用TLS1.3传输层安全协议:与早期的TLS版本相比,TLS1.3有很多优点,但使用它的主要原因是握手需要更少的往返,这可以减少协议的延迟。那么,QUIC在TCP/IP协议族中属于哪一层呢?我们知道QUIC是基于UDP实现的,是HTTP/3所依赖的协议。那么按照TCP/IP的分层,它属于传输层,也就是和TCP、UDP属于同一层。如果再详细一点,因为QUIC不仅承担了传输层协议的责任,还具备TLS的安全相关能力,可以通过下图来理解QUIC在HTTP/3实现中的地位。接下来我们单独分析一下QUIC协议。我们先看看他是如何建立连接的。QUIC的连接已经建立。我们知道TCP这种可靠的传输协议是需要三次握手的。正是因为三次握手,它需要额外消耗1.5个RTT,如果加上TLS,则需要消耗3-4个RTT连接。那么,QUIC是如何建立连接的呢?如何降低RTT?QUIC提出了一种新的连接建立机制。基于这种连接连接机制,实现了快速握手功能。QUIC连接建立可以使用0-RTT或者1-RTT建立连接来实现。QUIC在握手过程中使用Diffie-Hellman算法来保证数据交互的安全性,并将其加密和握手过程相结合,减少连接建立过程中的往返次数。Diffie–Hellman(以下简称DH)密钥交换是一种特殊的密钥交换方法。它是密码学领域最早付诸实践的密钥交换方法之一。DH允许双方在完全缺乏对方(私有)信息的前提下,通过不安全的通道达成共享密钥。该密钥用于后续信息交换的对称加密。建立QUIC连接的整体过程大致如下:QUIC在握手过程中使用Diffie-Hellman算法协商一个初始密钥。初始密钥取决于服务器存储的一组配置参数,这些配置参数会定期更新。初始密钥协商成功后,服务器会提供一个临时的随机数,双方根据这个随机数生成会话密钥。客户端和服务器将使用新密钥进行数据加密和解密。上述过程主要分为两个步骤:初始握手(Initialhandshake)、最终(和重复)握手(Final(andrepeat)handshake),分别介绍这两个过程。初始握手(Initialhandshake)当连接开始建立时,客户端会向服务器发送一个问候消息,(inchoateclienthello(CHLO)),因为是第一次建立,所以服务器会返回一个拒绝消息(REJ),表示握手未建立或密钥已过期。但是这个拒绝报文会包含更多的信息(配置参数),主要包括:ServerConfig:一个服务器配置,包括长期Diffie-Hellman公有值(longtermDiffie-Hellmanpublicvalue)服务器端Diffie的CertificateChain-Hellman算法:用于验证服务器的信任链SignatureoftheserverSignatureoftheserverConfig:使用信任链叶证书的公钥加密的ServerConfig签名Source-AddressToken:经过身份验证的加密块,包含公开的服务器的可见客户端IP地址和时间戳。客户端收到拒绝报文(REJ)后,会进行数据解析、签名验证等操作,然后缓存必要的配置。同时,客户端收到REJ后,会为本次连接随机生成一对自己的短期密钥(ephemeralDiffie-Hellmanprivatevalue)和短期公钥(ephemeralDiffie-Hellmanpublicvalue)。之后,客户端将自己刚刚生成的短期公钥打包成一个完整的CHLO消息包发送给服务器。这个请求的目的是将自己的短期密钥传输给服务器,方便前向保密,后面会详细介绍。向服务器发送CompleteCHLO报文后,为了减少RTT,客户端不会等待服务器的响应,而是立即传输数据。为了保证数据的安全,客户端通过计算自己的短期密钥和服务器返回的长期公钥,得到一个初始密钥(initialkeys)。有了这个主密钥,客户端就可以用这个密钥对自己要传输的信息进行加密,然后安全地传输给服务器。另一方面,服务端收到CompleteCHLO请求后,既有客户端的短期公钥,也有解析请求后自己保存的长期密钥。这样,服务端通过计算就可以得到一个与客户端完全相同的初始密钥(initialkeys)。接下来,当他收到客户端使用初始密钥加密的数据后,他可以使用这个初始密钥解密,他可以用这个初始密钥加密他的响应并返回给客户端。因此,从开始建立连接到数据传输,只消耗初始连接建立的1个RTT。最后(并重复)握手。那么,后续的数据传输是否可以使用初始密钥进行加密呢?其实不完全是因为初始密钥毕竟是根据服务器的长期公钥生成的,而在公钥过期之前,几乎所有的连接都使用同一个公钥,所以这种做法其实存在一定的危险.因此,为了实现前向保密(ForwardSecrecy)安全,客户端和服务端需要使用对方的短期公钥和自己的短期密钥进行计算。在密码学中,前向保密(英语:ForwardSecrecy,FS)是密码学中通信协议的一个安全属性,意思是长期使用的主密钥泄露不会导致过去会话密钥的泄露。所以现在的问题是,客户端的短期密钥已经发给了服务端,而服务端只把自己的长期密钥给了客户端,并没有给自己的短期密钥。因此,服务器收到CompleteCHLO后,会向服务器发送一个服务器问候(SHLO)消息,这个消息会用初始密钥(initialkeys)加密。这个CHLO消息包将包含一个由服务器重新生成的短期公钥。这样,客户端和服务器都拥有彼此的短期公钥(临时Diffie-Hellman公钥)。这样,客户端和服务器端都可以根据自己的短期密钥和对方的短期公钥进行运算,生成一个仅限于本次连接的前向安全密钥(Forward-SecureKey)。后续的请求发送,加密和解密都是基于这个密钥。这样,双方就完成了最终的密钥交换,连接握手,建立QUIC连接。下次重新建立连接时,客户端会从缓存中取出之前缓存的服务器的长期公钥,重新创建一个短期密钥,重新生成一个初始密钥,然后使用这个初始密钥对要加密传输的数据,只要向服务器发送一个CompleteCHLO请求即可。这实现了0RTT的数据传输。因此,如果有缓存的长期公钥,则直接进行数据传输,准备时间在0RTT以上。通过使用Diffie-Hellman算法协商密钥,结合加密和握手过程,大大降低了连接过程的RTT,使得基于QUIC的连接建立可以少至1RTT甚至0RTT。下面是谷歌官网上关于建立QUIC连接的流程图,可以帮助大家理解这个过程。另外,通过上面握手建立的过程,我们也可以知道QUIC在整个过程中通过加密和解密很好的保证了安全性。基于TCP协议的多路复用HTTP的最大问题之一是队头阻塞问题。那么,在这方面,QUIC是如何解决这个问题的呢?在TCP传输过程中,数据会被拆分成一个个按顺序排列的数据包,这些数据包通过网络传输到接收端,接收端将这些数据包按顺序组合成原始数据,从而完成数据传输。但是,如果其中一个数据包没有按顺序到达,接收端就会一直保持连接等待数据包返回,此时,后续的请求就会被阻塞。这会导致TCP队首阻塞。与HTTP/2类似,QUIC可以在同一个物理连接上拥有多个独立的逻辑数据流。这些数据流在同一个连接上并行传输,多个数据流之间的传输没有时序要求,也不会相互影响。数据流(Streams)在QUIC中提供了一种轻量级的、有序的字节流抽象。QUIC中单个数据流可以保证有序传递,但多个数据流可能会出现乱序。这意味着单个数据流的传输是顺序的,但是多个数据流中接收方接收到的顺序可能与发送方发送的顺序不同!也就是说,同一个连接上的多个数据流之间不存在连接依赖(不需要按顺序到达),即使某个数据包没有到达,也只会影响自己的数据流,并不会影响其他数据流。对于连接迁移,需要通过服务端和客户端通过两边的ip和port这四个参数来进行TCP连接的识别。在网络切换的场景下,比如手机切换网络,自身的ip会发生变化。这会导致之前的TCP连接失败,需要重新建立。这种场景在移动设备普及的今天是比较常见的。所以,在这一点上,QUIC被优化了。QUIC协议使用唯一的UUID来标记每个连接。当网络环境发生变化时,只要UUID不变,就可以不握手继续传输数据。可靠性TCP之所以被称为可靠链路,不仅是因为它有三次握手和四次关闭过程,还因为它做了很多流量控制、数据重传、拥塞控制等可靠性保证.这就是为什么TCP一直是HTTP实现的重要协议。那么,QUIC要想取代TCP,就需要在这方面下功夫。毕竟UDP本身是不具备这些能力的。TCP拥塞控制是TCP避免网络拥塞的一种算法,是Internet上主要的拥塞控制措施。经典的算法实现有很多,比如TCPTahoe和Reno、TCPVegas、TCPHybla、TCPNewReno、TCPWestwood和Westwood+、TCPBIC和CUBIC等。QUIC协议也实现了拥塞控制。不依赖于特定的拥塞控制算法,并提供可插拔接口以允许用户进行实验。缺省情况下,使用TCP协议的Cubic拥塞控制算法。在流量控制方面,QUIC提供了基于流和连接的两个级别的流量控制,既需要单独的Stream控制,也需要对所有Streams进行整体控制。QUIC的连接级流量控制用于限制QUIC接收方愿意分配给连接的总缓冲区,防止服务器为客户端分配任意大的缓冲区。连接级流控的过程与流级流控基本相同,只是转发数据和接收数据的偏移限制是所有流的总和。缺点上面我们介绍了QUIC相对于TCP的诸多优点。可以说这个协议确实优于TCP。因为它是基于UDP的,所以UDP协议本身并没有改变,只是做了一些增强。虽然可以避免中间装备死板的问题,但在推广上也不是完全没有问题。首先,很多企业、运营商和组织拦截或限制53端口(DNS)以外的UDP流量,因为这些流量最近经常被滥用于攻击。特别是,一些现有的UDP协议和实现容易受到放大攻击。攻击者可以控制无辜主机向受害者发送大量流量。因此,基于UDP的QUIC协议的传输可能会被阻塞。另外由于UDP一直被定位为不可靠连接,很多中间设备对它的支持和优化程度不高,所以丢包的可能性还是比较高的。总结下表是我总结的HTTP/2和HTTP/3的异同点。其中一些在本文中已经介绍过,还有一些我个人认为不是特别重要。本文中未提及它们。有兴趣的可以自学。
