HTTP 各个版本间有什么区别?
HTTP 1.0
HTTP 1.0 是最早的 HTTP 版本,它于 1996 年发布,是基于 TCP/IP 协议实现的。
由于只支持短连接和单一连接,每次通信都要建立一个 TCP 连接,只有上次请求响应后才会释放连接,所以会造成请求的队头阻塞。
HTTP 1.1
HTTP 1.1 相比 HTTP 1.0 做出了一些性能上的改进:
- 长连接(持久连接)
只要任意一端没有明确提出断开连接,就保持 TCP 连接状态。
- 管道网络传输
同一 TCP 连接中,客户端可以同时发起多个请求。
虽然支持同时发送多个请求了,但由于服务器必须按照请求的顺序发送对这些请求的响应,当服务端处理某一请求耗时较长时,后续请求的处理都会被阻塞住。这也就造成了响应的队头阻塞。
HTTP 2
从 HTTP2 开始,HTTP 的性能有了较大的改进:
- 头部压缩
在早先的版本中,即使多个请求的头部相同,每次发送请求时都会重复发送,这就造成了大量的性能浪费。
HTTP2 引入了头部压缩,只要多个请求的头是一样的或相似的,就会消除重复的部分,提高了传输速度。
这其实是基于 HPACK 算法实现的:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号。
- 二进制格式
HTTP2 不再使用纯文本形式的报文,头信息和数据体都是二进制,并且统称为帧。
计算机收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输效率。
- 服务器推送
HTTP2 在一定程度上改善了传统的请求 - 应答工作模式,服务端不再被动响应,可以主动向客户端发送消息。
当浏览器向服务器请求一个 HTML 文件时,后续 CSS 或 JS 文件可以不再由浏览器再次发起请求获取,而可以通过服务器主动推送,减少了消息传递的次数。
- 并发传输
HTTP2 引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接。
针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就使得 HTTP2 可以并行地发送请求和响应。
虽然这解决了 HTTP1.1 中响应的队头阻塞问题,但由于HTTP2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用。
所以,当前一个字节数据没到达时,后收到的字节数据只能存放在内核缓冲区中,只有等到前一字节数据到达后,HTTP2 应用层才能从内核中拿到数据,这就是 HTTP2 中 TCP 层的队头阻塞。
HTTP 3
HTTP1.1 和 HTTP2 都有队头阻塞的问题,HTTP2 的队头阻塞是 TCP 层导致的,所以在 HTTP3 中,HTTP 下层的协议改成了基于 UDP 的 QUIC 协议。
UDP 协议不管顺序也不管丢包,所以不会出现阻塞,虽然 UDP 是面向不可靠传输的协议,但基于 UDP 的 QUIC 协议能实现类似 TCP 的可靠传输。
- 无队头阻塞
QUIC 协议同样可以在同一条连接上并发传输多个 Stream,但当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。
- 更快的连接建立
HTTP1 和 HTTP2 协议中,TCP 和 TLS 是分层的,分别属于传输层和表示层,所以它们需要分批次握手,先 TCP 握手,再 TLS 握手。
而 HTTP3 的 QUIC 协议内部包含了 TLS,在传输数据前虽然需要 QUIC 协议握手,但是这个握手过程只需要 1 RTT。
- 连接迁移
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)来确定一条 TCP 连接。
当设备的网络环境发生变化导致 IP 地址和端口发生变化时,需要将原有的连接迁移到新的地址和端口。这就意味着要断开连接并重新建立连接,就会导致出现卡顿。
而 QUIC 协议是通过连接ID标记通信的两个端点的。客户端和服务端会使用各自的 ID 来标记自己,并在握手的过程中交换双方的连接ID。
之后即使 IP 地址发生变化,只要连接ID等上下文信息仍然保存着,就能复用原来的连接,这就达到了连接迁移的功能。