HTTP(超文本传输协议)是构建 web 的基础协议。随着互联网的飞速发展,HTTP 协议经历了多次升级,从最初的 HTTP/1.0 到现在的 HTTP/3.0,每次更新都在性能、效率和可扩展性上做了显著优化。本文将详细探讨 HTTP 各版本的特性与演变过程。
HTTP 1.0
特点:
-
无连接:每次请求都需要与服务器建立新的 TCP 连接,服务器处理完成后会立即关闭连接。这意味着每个请求都需要消耗额外的时间和资源来建立连接。开启长连接需要在请求头里指定
Connection:keep-alive - 无状态:服务器不记录任何客户端的状态信息,无法追踪客户端请求的历史。每次请求都是独立的,无法了解请求之前发生的情况。
状态管理:
- 借助cookie/session机制来做身份认证和状态记录。
存在的问题:
- 无法复用连接:每次请求都需要建立新的连接,导致连接建立和断开的开销过大。
- 队头阻塞:由于每个 TCP 连接只处理一个请求,前一个请求的响应必须完全完成,后一个请求才能开始,导致请求队列阻塞。
HTTP 1.1
主要改进:
- 默认开启长连接:HTTP/1.1 可以在同一个 TCP 连接上复用多个请求。客户端和服务器可以在一个连接上发送多个请求和响应,减少了连接建立的开销。
- 请求管道化:HTTP/1.1 支持请求管道化,客户端可以在等待响应的同时发送后续请求,无需等待前一个请求的响应完成。虽然这样可以提高效率,但由于 队头阻塞 的存在,前一个请求的响应仍然需要先返回,才能开始下一个请求的处理。
-
增加 Host 字段:在 HTTP/1.0 中,HTTP 请求必须包含目标服务器的 IP 地址,因此每个网站都需要一个单独的 IP 地址。HTTP/1.1 引入了
Host字段,允许一个服务器托管多个网站(虚拟主机),便于对多个域名进行管理。虚拟主机:虚拟主机允许多个网站共享同一个物理服务器和 IP 地址,每个网站通过不同的域名来区分。例如,www.example.com 和 www.another.com 可以共享一个服务器的 IP 地址,但使用不同的域名。Host 字段:HTTP/1.1 引入的 Host 请求头字段指定了客户端希望访问的主机名和端口号。当客户端发送请求时,HTTP 请求头中会包含该字段。 -
支持断点传输:可以通过
Range请求头实现文件的断点续传。
队头阻塞问题:
尽管 HTTP/1.1 支持请求管道化,但由于其仍然依赖于单一的 TCP 连接进行请求处理,因此仍然存在队头阻塞的问题,即一个请求的响应延迟可能影响后续请求的处理。
其实就是把阻塞问题从客户端转移到服务端了。比如在管道化的情况下,客户端依次发出了3个请求,服务端虽然能并发处理这3个请求,但仍旧需要按请求的顺序返回响应,防止请求乱序。在请求1响应完成后,才会去响应请求2。如果请求1的任务耗时较多,就会导致请求2和3的响应被阻塞住。

- 请求报文
POST /submit_form HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Content-Length: 34
Connection: keep-alive
name=John&age=30&city=NewYork
HTTP 2.0
主要改进:
- 二进制分帧:每个数据流(Stream)以消息的形式发送,而消息由一或多个帧(Frame)组成。这些帧可以乱序发送,然后再根据每个帧头部的流标识符(Stream_id)重新封装。
-
头部压缩:客户端和服务端双方各自维护header cache的索引表,对于预设好的或者被保存下来的信息只需要发送对应的索引值就行,从而减少头部的大小,提高传输效率。
采用 HPACK 算法,包含静态表和动态表,静态表由 HTTP/2 协议标准预先定义好,固定不变,包含头部名称和其对应的索引值,索引范围1~61。比如:method:GET是2, Host的索引是38;动态表添加会话期间出现的允许被保存的头部字段(k)和值(v)。索引表中存储的可能kv,也可能只是k,不同的存储值在发送时进行二进制编码的标识位也不一样。
静态表部分内容
例如
:method:GET是kv均被索引了,被编码成0x82,二进制10000010,第一位是1表示kv均被索引了,索引值为2;:method:DELETE只有k被索引了,被编码成0x42,二进制01000010,前两位是01表示k被索引,v没被索引,索引值为2,后续value部分(DELETE)还得被单独编码。
- 多路复用:使用多个stream(流),每个stream又分帧传输,使得一个tcp连接能够处理多个http请求
- 服务器推送:HTTP/2 允许服务器主动向客户端推送资源,比如在客户端请求一个 HTML 页面时,服务器可以主动推送与之相关的 JavaScript 文件和 CSS 文件。
队头阻塞问题:
尽管 HTTP/2 解决了请求队头阻塞问题,但它仍然依赖于 TCP 协议。而TCP又保证了数据的有序性和完整性(通过确认应答与序列号、超时重传等机制)。这意味着在同一个TCP通道中,如果前一个包丢失,后续的所有数据包都会被阻塞直到丢失的数据包被重传。
HTTP 3.0
主要改进:
- 基于 QUIC 协议:HTTP/3 基于 Google 的 QUIC 协议,而 QUIC 使用的是 UDP 协议而非 TCP。QUIC 解决了许多 TCP 和 TLS 中存在的问题,尤其是在连接建立和重传效率方面。
- 减少握手时间:TCP + TLS 协议需要两次往返时间(2-RTT)来完成连接建立和加密握手。而 QUIC 协议仅需要1-RTT 来完成初次连接,后续连接可以通过缓存的会话信息0-RTT 建立,显著减少了延迟。
- 解决流阻塞问题:HTTP/3 实现了多路传输,每个流都是独立的。如果一个流中的数据包丢失,只会影响该流的数据传输,不会阻塞其他流。与 HTTP/2 相比,QUIC 的多路复用机制大大减少了队头阻塞的情况。
-
重传优化:重传也是针对流来说的,而HTTP2.0重传是针对连接的。每个数据包都有一个唯一的包编号,这个编号是全局递增的,而不是像TCP那样基于字节的序列号,这在丢包检测和重传时效率更高。
举个例子: 在 TCP 中,接收方做出应答时,需要根据TCP包的数据部分的字节数和TCP包的序列号去计算出ACK包的序列号,这涉及到对字节流大小的计算。而在 QUIC 中,接收方仅需要关注 包编号,例如接收到包编号 1、3、4,但没有收到包编号 2,接收方立即知道包编号 2 的数据包丢失,发出重传请求。无需进行复杂的字节序列号计算。 - 连接迁移:QUIC 使用 64 位随机数来标识连接,而不是 TCP 的四元组(源地址、源端口、目的地址、目的端口),从而支持在不同网络环境下迁移连接。
QUIC 和 HTTP/2 的对比:
QUIC 在每个流级别管理重传,而 HTTP/2 在连接级别管理重传。因此,QUIC 的重传效率更高,不会造成其他流的阻塞。
总结
从 HTTP/1.0 到 HTTP/3.0,每个版本的演进都在优化网络传输的效率与性能。HTTP/1.0 和 HTTP/1.1 都在解决无状态、无连接和队头阻塞问题方面有所改进,而 HTTP/2 引入了多路复用和二进制分帧,进一步提升了传输效率。HTTP/3 基于 QUIC 协议,不仅减少了握手延迟,还解决了流的独立性和连接迁移等问题,为现代互联网提供了更为高效的解决方案。
每个协议的演进都紧跟着互联网技术的发展,不断提高用户体验,优化网络传输效率和安全性。
