HTTP/2 数据包作为一个或多个 TCP 数据包发送。就像 TCP 数据包最终作为 IP 数据包(或数据报)发送一样。
这确实意味着即使 HTTP/2 在应用层 (HTTP) 进行了多路复用,但它在传输层 (TCP) 并没有真正独立的流,而 HTTP/2 的一个问题是我们刚刚移动了头部从 HTTP 层到 TCP 层的线路 (HOL) 阻塞问题。
我们来看一个例子:一个示例网页需要下载10张图片才能显示。
在 HTTP/1.1 下,浏览器会打开一个 TCP 连接,触发第一个请求,然后因为无法使用该 TCP 连接发出后续请求而被卡住。尽管事实上 TCP 连接在得到响应之前什么都不做,并且在 TCP 层没有任何东西阻止它。这纯粹是一个 HTTP 限制,主要是因为 HTTP/1 是基于文本的,因此不可能混合一些请求。 HTTP/1.1 确实有 HTTP 管道的概念,它允许发送后续请求,但它们仍然必须按顺序返回。而且它的支持很差。相反,作为一种解决方法,浏览器打开了多个连接(通常为 6 个),但这也有很多缺点(创建速度慢、无法跟上速度并且无法在它们之间划分优先级)。
HTTP/2 允许在同一个 TCP 连接上发送这些后续请求,然后以任何顺序接收所有请求的位并将它们拼凑在一起进行处理。因此,请求的第一个图像实际上可能是最后收到的图像。这对于慢速连接(发送延迟占总时间的很大一部分)或服务器可能需要一段时间处理某些请求(例如,如果必须从磁盘获取第一张图像但第二个已经在缓存中可用,那么为什么不使用连接来发送第二个图像)。这就是为什么 HTTP/2 通常比 HTTP/1.1 更快更好——因为它更好地使用 TCP 连接并且没有那么浪费。
但是,由于 TCP 是一种有保证的有序协议,它不知道更高级别的应用程序 (HTTP) 将其用于什么目的,如果 TCP 数据包丢失,这确实会给 HTTP/2 带来一些问题。
假设这 10 张图片都是按顺序返回的。但是来自第一个图像的数据包丢失了。理论上,如果 HTTP/2 真的由独立的流组成,浏览器可以显示最后 9 张图像,然后重新请求丢失的 TCP 数据包,然后显示第一张图像。相反,在 TCP 让上层 HTTP 知道收到了哪些消息之前,所有 10 个图像都被搁置等待丢失的 TCP 数据包重新发送。
因此,在有损环境中,HTTP/2 的性能明显低于具有 6 个连接的 HTTP/1.1。
这在创建 HTTP/2 时是众所周知的,但在大多数情况下,HTTP/2 更快,所以他们还是发布了它,直到他们能够修复这种情况。
HTTP/3 旨在解决剩下的情况。它通过从 TCP 转移到一个名为 QUIC 的新协议来实现这一点,该协议与 TCP 不同,它是根据内置的多路复用的思想制作的。 QUIC 建立在 UDP 之上,而不是尝试创建一个全新的低级协议,因为它得到了很好的支持。但是 QUIC 非常复杂,需要一段时间才能到达这里,这就是为什么他们没有坚持使用 HTTP/2 来拥有它,而是在此过程中发布了他们拥有的东西。