HTTP3简述


发布于 2020-04-09 / 59 阅读 / 0 评论 /
HTTP3 与 HTTP2 几乎相同,二者的主要区别就体现在 QUIC/TCP 之上各项功能的技术实现。

1.HTTP3概述

HTTP3甩掉了TCP、TSL的包袱,构建高效网络QUIC协议。

HTTP3使用了UDP协议,基于UDP实现了类似TCP的多路数据流、传输可靠性等功能,将这套功能称为QUIC协议。

2.HTTP3解决的问题

HTTP3从以下三个角度解决了HTTP2中产生的问题:

(1)HTTP3基于UDP协议重新定义了连接,在QUIC层实现了无序、并发字节流的传输,解决了队头阻塞问题(包括基于QPACK解决了动态表的队头阻塞)。

(2)HTTP3重新定义了TLS协议加密QUIC头部的方法,既提高了网络攻击成本,又降低了建立连接的速度(仅需1个RTT就可以同时完成建链与密钥协商)。

(3)HTTP3将Packet、QUIC Frame、HTTP3 Frame分离,实现了连接迁移功能,降低了5G环境下高速移动设备的连接维护成本。

3.HTTP3与HTTP2的协议层次

HTTP3的协议结构如下图所示:

HTTP2与HTTP3采用二进制、静态表、动态表与Huffman算法对HTTP Header编码,不只提供了高压缩率,还加快了发送端编码、接收端解码的速度。

4.HTTP3特性

HTTP3有以下四个特性:

(1)基于UDP协议改造,实现了快速握手。

(2)集成了TLS的加密功能

(3)多路复用,彻底解决了头阻塞问题,一个物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输。

(4)实现了类似TCP的流量控制、传输可靠性的功能。

5.QUIC流量控制

TCP流量控制:接收方告诉发送方自己的接收窗口大小,发送方据此调整自己发送的数据量。

QUIC实现了两种级别的流量控制:

(1)stream级别的流量控制:每个流都有独立的滑动窗口,每个流都可以做流量控制,防止单个流消耗掉连接的全部接收窗口。

(2)connection级别的流量控制:限制连接中所有流的发送数据量,防止其超过各个接收窗口大小之和。

6.HTTP3报文结构

在IoT时代,移动设备接入的网络会频繁变动,从而导致设备IP地址改变。对于通过四元组(源IP、源端口、目的IP、目的端口)定位连接的TCP协议来说,这意味着连接需要断开重连,所以上述2个RTT的建链时延、TCP慢启动都需要重新来过。而HTTP3的QUIC层实现了连接迁移功能,允许移动设备更换IP地址后,只要仍保有上下文信息(比如连接ID、TLS密钥等),就可以复用原连接。

HTTP3的报文结构如下图所示:

在UDP报文头部与HTTP消息之间,共有3层头部,定义连接且实现了Connection Migration主要是在Packet Header中完成的。这3层Header实现的功能各不相同:

(1)Packet Header实现了可靠的连接。当UDP报文丢失后,通过Packet Header中的Packet Number实现报文重传。连接也是通过其中的Connection ID字段定义的。

(2)QUIC Frame Header:在无序的Packet报文中,基于QUIC Stream概念实现了有序的字节流,这允许HTTP消息可以像在TCP连接上一样传输。

(3)HTTP3 Frame Header:定义了HTTP Header、Body的格式,以及服务器推送、QPACK编解码流等功能。

6.1.Packet Header

Packet Header实现了可靠的连接。当UDP报文丢失后,通过Packet Header中的Packet Number实现报文重传。连接也是通过其中的Connection ID字段定义的。

为了进一步提升网络传输效率,Packet Header又可以细分为两种:Long Packet Header用于首次建立连接;Short Packet Header用于日常传输数据。

6.1.1.Long Packet Header

Long Packet Header用于首次建立连接,其格式如下图所示:

建立连接时,连接是由服务器通过Source Connection ID字段分配的,这样,后续传输时,双方只需要固定住Destination Connection ID,就可以在客户端IP地址、端口变化后,绕过UDP四元组(与TCP四元组相同),实现连接迁移功能。

6.1.2.Short Packet Header

Short Packet Header用于日常数据传输,这里就不再需要传输Source Connection ID字段了,具体格式如下图所示:

图中的Packet Number是每个报文独一无二的序号,基于它可以实现丢失报文的精准重发。如果你通过抓包观察Packet Header,会发现Packet Number被TLS层加密保护了,这是为了防范各类网络攻击的一种设计。

6.2.QUIC Frame Header

QUIC Frame Header在无序的Packet报文中,基于QUIC Stream概念实现了有序的字节流,这允许HTTP消息可以像在TCP连接上一样传输。Stream之间可以实现真正的并发。

QUIC Stream Frame定义了有序字节流(Stream),且多个Stream间的传输没有时序性要求,这样,HTTP消息基于QUIC Stream就实现了真正的多路复用,队头阻塞问题自然就被解决掉了。

HTTP3 Stream借鉴了HTTP2 Stream中的概念,不过是把HTTP2 frame拆解位两层。

一个Packet报文中可以存放多个QUIC Frame,如下图所示:

所有Frame的长度之和不能大于PMTUD(Path Maximum Transmission Unit Discovery,这是大于1200字节的值),我们可以把它与IP路由中的MTU概念对照理解。

每个Frame有明确的类型,具体结构如下图所示:

前4个字节表示Frame Type,下表是各类Frame对应的Type值。

下面,我们主要通过分析0x08-0x0f这8中STREAM类型的Frame,来搞清楚Stream流的实现原理。

Stream Frame头部的3个字段,完成了多路复用、有序字节流以及报文段层面的二进制分隔功能,包括:

(1)Stream ID:标识了一个有序字节流。当HTTP Body非常大,需要跨越多个Packet时,只要在每个Stream Frame中含有同样的Stream ID,就可以传输任意长度的消息。多个并发传输的HTTP消息,通过不同的Stream ID加以区别。

(2)Offset:消息序列化后的“有序”特性,是通过Offset字段完成的,它类似于TCP协议中的Sequence序号,用于实现Stream内多个Frame间的累计确认功能;

(3)Length:指明了Frame数据的长度。

Stream Frame用于传递HTTP消息,格式如下图所示:

你可能会奇怪,为什么会有8种Stream Frame呢?这是因为0x08-0x0f 这8种类型其实是由3个二进制位组成,它们实现了以下3 标志位的组合:

(1)第1位表示是否含有Offset,当它为0时,表示这是Stream中的起始Frame,这也是上图中Offset是可选字段的原因。

(2)第2位表示是否含有Length字段。

(3)第3位Fin,表示这是Stream中最后1个Frame,与HTTP2协议Frame帧中的FIN标志位相同。

Stream Data中并不直接存放HTTP消息,因为HTTP3还需要实现服务器推送、权重优先级设定、流量控制等功能,所以Stream Data中手续爱你存放了HTTP3 Frame,结构如下图所示:

其中,Length指定了HTTP消息的长度,而Type字段包含以下类型:

(1)0x00:DATA帧,用于传输HTTP Body包体;

(2)0x01:HEADERS帧,通过QPACK 编码,传输HTTP Header头部;

(3)0x03:CANCEL_PUSH控制帧,用于取消1次服务器推送消息,通常客户端在收到PUSH_PROMISE帧后,通过它告知服务器不需要这次推送;

(4)0x04:SETTINGS控制帧,设置各类通讯参数;

(5)0x05:PUSH_PROMISE帧,用于服务器推送HTTP Body前,先将HTTP Header头部发给客户端,流程与HTTP2相似;

(6)0x07:GOAWAY控制帧,用于关闭连接(注意,不是关闭Stream);

(7)0x0d:MAX_PUSH_ID,客户端用来限制服务器推送消息数量的控制帧。

6.3.HTTP3 Frame Header

HTTP3 Frame Header定义了HTTP Header、Body的格式,以及服务器推送、QPACK编解码流等功能。

HTTP2中对HTTP Header使用HPACK编码,采用静态表、动态表及Huffman编码,例如下图所示:

静态表的使用上,HTTP3使用的QPACK与HTTP2的HPACK相似,不过HTTP2的静态表有61个表项,而HTTP3的静态表有98个表项。

在Huffman编码方式上,HTTP3和HTTP2都是一样的。

在动态表上,使用差距有点大。动态表就是将未包含在静态表中的Header项,在其首次出现时加入动态表,这样后续传输时仅用1个数字表示,大大提升了编码效率。因此,动态表是天然具备时序性的,如果首次出现的请求出现了丢包,后续请求解码HPACK头部时,一定会被阻塞!

QPACK是如何解决队头阻塞问题的呢?事实上,QPACK将动态表的编码、解码独立在单向Stream中传输,仅当单向Stream中的动态表编码成功后,接收端才能解码双向Stream上HTTP消息里的动态表索引。

我们又引入了单向Stream和双向Stream概念,不要头疼,它其实很简单。单向指只有一端可以发送消息,双向则指两端都可以发送消息。还记得上一小节的QUIC Stream Frame头部吗?其中的Stream ID别有玄机,除了标识Stream外,它的低2位还可以表达以下组合:

因此,当Stream ID是0(0000)、4(0100)、8(1000)、12(1100)时,这就是客户端发起的双向Stream(HTTP3不支持服务器发起双向Stream),它用于传输HTTP请求与响应。单向Stream有很多用途,所以它在数据前又多出一个Stream Type字段,Stream Type有以下取值:

(1)0x00:控制Stream,传递各类Stream控制消息。

(2)0x01:服务器推送消息。

(3)0x02:用于编码QPACK动态表,比如面对不属于静态表的HTTP请求头部,客户端可以通过这个Stream发送动态表编码。

(4)0x03:用于通知编码端QPACK动态表的更新结果。

由于HTTP3的STREAM之间是乱序传输的,因此,若先发送的编码Stream后到达,双向Stream中的QPACK头部就无法解码,此时传输HTTP消息的双向Stream就会进入Block阻塞状态(两端可以通过控制帧定义阻塞Stream的处理方式)。

QPACK使用独立的单向Stream分别传输动态表编码、解码信息,这样乱序、并发传输HTTP消息的Stream既不会出现队头阻塞,也能基于时序性大幅压缩HTTP Header的体积。