【网络】QUIC协议和HTTP3
面试常考,临时抱佛脚学一下。参考 https://zhuanlan.zhihu.com/p/266578819;
HTTP1.0和HTTP1.1
HTTP1.0中,每一个请求必须等上一个请求收到响应了才能继续。且每一次请求都会重新建立TCP链接。
HTTP1.1中可以复用TCP链接,但是依旧没有解决队头阻塞的问题,虽然复用了TCP链接,但是请求B依旧需要等待请求A的响应,才能继续发送。
换句话说,服务器发送响应必须严格按照客户端发送请求的顺序。客户端按ABC发送,服务端也需要按ABC返回响应。
HTTP2的升级
总结就是:
- steam并发传输;
- 哈夫曼编码HPACK压缩header为二进制后传输;
- 服务器可以主动推送资源给客户端;
并发传输就是在HTTP中添加一个Steam ID,通过ID来标识不同的HTTP请求报文,这样就能在同一个TCP传输流中并发传输多个HTTP报文。而在HTTP1.1中,虽然可以复用同一个TCP链接,但是HTTP1.1中在某个请求没有收到响应之前,是无法传输其他请求的。
压缩header:通过哈夫曼编码在双方建立一个动态表,其中包括61项常用header的静态表,同时可以对静态表进行动态扩展(需要使用但不在61项中的header),最终发送header的时候,会用序号来替代header的字符串,同时对header的value进行压缩,从而减少HTTP包的长度。
服务器主动推送:请求一个资源后,服务器可以主动将其他客户端用的上的资源推送给客户端,而不需要等客户端主动请求。比如请求index.html
后,将需要的js/css也推送给客户端。
QUIC可靠性保证
其实这个问题就是UDP如何保证可靠性(加什么东西?),把TCP可靠性机制往里面加就行了。
- 重传机制
- 确认机制
- 握手机制(快速握手)
- 拥塞控制(应用层处理的)
- 多路复用(多个stream并发)
- 前向纠错(校验码)
QUIC的特点
无队头阻塞
无队头阻塞:HTTP2中用tcp实现多stream并发传输,但是tcp本身需要保证数据到达应用层的有序性。假设有这样的报文到达顺序
1 | stream1 tcp1 |
此时tcp3出现了丢包,虽然我们客户端已经收到了服务器发送的stream2(tcp4)和stream3(tcp5),但因为tcp协议栈需要维护序号有序性,没有收到tcp3之前,它不会把后序的数据交付给上层,这就导致了stream2/3因为stream1的丢包而出现了阻塞,也就是队头阻塞
的含义。
而QUIC基于UDP,因为UDP本身是无连接的,所以它不会出现队头阻塞的问题。
1 | stream1 udp3 # 丢包 |
即便udp3丢包了,也不会影响udp4/5,此时steam2/3也能被正常交付给上层供HTTP使用。
反映到浏览器加载中,有可能这里的udp4/5就是steam2/3的最后一个分片,交付给上层后就已组成了一个完整的HTTP报文,表现在浏览器上就是某一个模块加载出来了,会让用户的体验更好。
更快的链接建立
在通过TCP实现HTTPS的时候,需要涉及到TCP三次握手和TLS/SSL握手。其中TCP三次握手和SSL握手都要进行3次数据传输,一共就是6次单向传输了(3RTT)。
而UDP实现的QUIC也需要握手,这个过程是1RTT。但是QUIC本身就是一个应用层实现的协议,它可以直接在第一次链接的时候就携带TLS/SSL的相关信息,第二次链接的时候发送QUIC握手报文+TLS握手报文和数据载荷,同时完成TLS握手的功能,近似实现0RTT的握手。
这里涉及到了TLS1.3,先暂且不去深入了解,知道大概就行。
链接迁移
在基于TCP的HTTP中,需要通过IP和TCP端口的四元组来定位客户端和服务端。
假设我们从WIFI切换成4G/5G,此时客户端的ip和端口肯定会发生变化,而在旧版本中,这就需要使用TCP重新和服务器进行三次握手和TLS链接建立,才能重新开始数据传输。
而QUIC中,除了IP/UDP的四元组外,还有个独特的QUIC会话ID来标识两边的传输。
假设一个客户端从WIFI切换成5G,发生了IP和端口的变化,但它发送的QUIC请求报文中,依旧包含了之前的QUIC会话ID,此时服务器可以通过这个会话ID定位之前的传输,并继续传输之前中断的时候的数据。
再加上前文提到的QUIC握手次数少,这两个相结合,客户端切换IP和端口的时间消耗就更少了!
HTTP3的QPACK
在HTTP2中,会用HPACK来将更新后的动态header表发送给对方,但是它的HPACK并没有确认机制。也就是说,如果你发送的一个新的HPACK更新丢包了,对方没有收到,也就不认识某个新的header序号,此时客户端发送的信息就没有办法被正常解码了,此时会出现阻塞。
而QPACK就是在HPACK的基础上加入了确认机制。有两个链接来进行
- 一个叫 QPACK Encoder Stream,用于将一个字典(Key-Value)传递给对方,比如面对不属于静态表的 HTTP 请求头部,客户端可以通过这个 Stream 发送字典;
- 一个叫 QPACK Decoder Stream,用于响应对方,告诉它刚发的字典已经更新到自己的本地动态表了,后续就可以使用这个字典来编码了。
这两个特殊的单向流是用来同步双方的动态表,编码方收到解码方更新确认的通知后,才使用动态表编码 HTTP 头部。
当A给B发送的动态表没有被收到时,A还不会对这个新的header进行压缩(以原始形式进行发送)。直到收到B对这个动态表的响应了,才会压缩传输。
这样就避免了HPACK中A会压缩发送一个B还不知道的动态表中的header的情况。