为什么我们需要HTTP/3呢?一个重要原因是解决了“头阻塞”问题。
HTTP/2中的头阻塞问题
HTTP/2通过帧和流在HTTP级别解决了头阻塞问题。但是,在TCP级别问题仍然存在。
在接收来自上层的帧后,TCP会将它们分成段。
如果一切顺利,所有段将到达另一端。
然而,互联网可能不稳定。在这个过程中,一些段可能会丢失。
TCP有一个保证传递的功能。它将接收到的段放入缓冲区,并等待丢失的段重新传输,从而导致头阻塞。
为了解决这个问题,我们需要找到TCP的替代品——QUIC和UDP。
更新的协议栈
从协议栈中可以看到一个重大的变化:TCP被UDP取代。
不同于TCP,UDP不保证传递,段之间没有依赖关系。这意味着不再会有头阻塞问题。
此外,由于UDP是一种无连接的协议,不需要握手。它运行速度比TCP更快。
在UDP的基础上,引入了一个新协议QUIC。它继承了TCP的一些优点,包括连接管理和流控制。此外,QUIC实现了一些功能来保证数据传递,以弥补UDP的不足。
另一个变化是在QUIC内部实现了TLS,同时继承了其所有安全特性。由于TLS 1.3已经投入生产,QUIC从这个版本开始。
最后但同样重要的是,QPACK取代了HPACK,进一步提高了头部压缩算法的性能。静态表中的条目从61增加到98,并且现在是0索引的。
QUIC数据包、帧和流
QUIC由数据包和帧组成。一个数据包由多个帧组成。
以下是QUIC数据包的结构。
在数据包头中,QUIC使用连接ID来标记其目标和源。
浏览器和服务器可以选择它们的ID。有了它们,我们可以将连接从IP和端口中解耦,实现平稳的连接迁移。
以下情况可能每天都会发生在您身上。
当你离开家时,你的手机会从WiFi切换到4G(很快是5G)。由于IP变化,TCP会重新连接。在重新连接到互联网之前,您将在一瞬间失去连接。
使用QUIC,连接ID保持不变,因此连接在概念上保持不变。尽管IP发生变化,但连接被重用,没有重新连接的成本。
接下来,让我们看一个QUIC数据包的示例。
QUIC IETF
QUIC连接信息
[数据包长度:1350]
1... .... = 头部形式:长头部(1)
.1.. .... = 固定位:True
..00 .... = 数据包类型:初始(0)
.... 00.. = 保留:0
.... ..00 = 包号长度:1字节(0)
版本:draft-29(0xff00001d)
目标连接ID长度:8
目标连接ID:45fb5955dfaa8914
源连接ID长度:0
令牌长度:0
长度:1332
数据包号码:1
负载:5a99e5b29413627619ca3b5add4cf8b6ce348355b1c1a2be9874c7961e7996a24aeec860…
TLSv1.3记录层:握手协议:客户端Hello
填充长度:997
从公共标志1100 0000中,我们可以知道它是一个长头部,其类型是初始的。接下来是QUIC版本:draft-29,然后是目标连接ID及其长度。
接下来,让我们看QUIC帧结构。
类似于HTTP/2帧,QUIC中有各种帧类型。
例如,STREAM帧用于携带流,而ACK帧用于控制。
标题中的字段使用可变长度编码,最多可达8字节。
流标识符可以达到2^62,其中两位保留为标记符。
- 最不显著的位标记发送方:0表示客户端,1表示服务器。
- 第二位最不显著的位标记流的方向:0表示双向流,1表示单向流。
以下是帧的示例。
- TLSv1.3记录层:握手协议:客户端Hello
- 帧类型:CRYPTO(0x0000000000000006)
- 偏移:0
- 长度:314
- 加密数据
- 握手协议:客户端Hello
- 帧类型是CRYPTO,这是为握手设计的类型,负载是加密数据。
下面是另一个示例,服务器Hello。
- TLSv1.3记录层:握手协议:服务器Hello
- 帧类型:CRYPTO(0x0000000000000006)
- 偏移:0
- 长度:90
- 加密数据
- 握手协议:服务器Hello
- 握手类型:服务器Hello(2)
- 长度:86
- 版本:TLS 1.2(0x0303)
- 随机数:0f58bdbd934450c7aa98242121447bef2fe0733aa5fc3beffab6513c7177f9a4
- 会话ID长度:0
- 密码套件:TLS_AES_128_GCM_SHA256(0x1301)
- 压缩方法:null(0)
- 扩展长度:46
- 扩展:key_share(len=36)
- 扩展:supported_versions(len=2)
除了QUIC帧的新字段外,其余的字段在TLS 1.3握手中都有提到。
HTTP/3协议和帧
QUIC可以完成很多工作,减轻了HTTP/3的工作负担。
例如,与HTTP/2不同,HTTP/3利用QUIC流,而不是自己定义和控制流。
在HTTP/2中管理的大多数帧类型都移到了QUIC,如RST_STREAM帧和WINDOW_UPDATE帧。
由于这一点,HTTP/3的帧结构简化为仅有2个字段——帧类型和长度。
有一点值得一提,HTTP/3没有像HTTPS的443端口一样的指定端口。
浏览器首先使用HTTP/2与服务器建立连接以发现服务。服务器会响应带有Alt-Svc头部的请求,其中包括HTTP/3的端口,例如Alt-Svc: h3-29=":443"。有了这个信息,浏览器异步地连接到该端口。一旦连接建立,将使用HTTP/3进行未来的通信。