一、HTTP1.0
HTTP 1.0为 http 协议的第二个版本。
它指定了浏览器与服务器只保持短暂的连接,即每次请求都要与服务器建立一个新的TCP连接,在服务器完成请求处理后立即断开TCP连接,服务器也不追踪每个客户也不记录过去的请求。
打个比方说,解析html文件,当发现文件中存在资源文件时,这时候又会创建单独的连接,最终导致一个html文件的访问包含了多次的请求和响应,每次请求都需要创建连接、关闭连接。
这种方式明显造成了性能上的缺陷。
如果我们需要建立长连接,需要设置一个非标准的 Connection 字段Connection: keep-alive
二、HTTP1.1相比HTTP1.0提高了什么性能
HTTP1.1相比HTTP1.0做了以下的性能优化:
- 持久连接。通过使用持久连接(长连接)的方式来改变
HTTP1.0短连接的性能缺陷,即在第一次请求时建立起的TCP连接,当第一次请求结束后也不会关闭,而后续的请求也可以继续利用该TCP连接。 - 管道网络传输。在
HTTP1.0中,必须要等一次请求结束后,才会进行下一次请求。为了解决该问题,引入了管道网络传输,使得在发送一次请求后,即使该请求没有完成,依然可以发送下一次请求。值得注意的是,服务器还是会按照请求的先后顺序依次返回响应结果。
看起来HTTP1.1已经挺完善了,但依然还是会有性能瓶颈:
- 头部未压缩。请求 / 响应头部未经压缩就发送,首部的信息越多延迟就越大。
HTTP1.1只能压缩Body的部分。 - 首部冗长。每次请求都会发送相同的首部,造成浪费。
- 队头阻塞。服务器是按顺序响应的,如果服务器响应慢,会导致客户端一直接收不到数据,而请求将会累积,从而导致队头阻塞。
- 没有优先级控制。
-
三、HTTP/2 做了什么优化
众所周知,HTTP/2 是基于 HTTPS 的,所以 HTTP/2 的安全性也是有所保障。

而HTTP/2 相比 HTTP/1.1 性能上的改进有以下几个方面: 头部压缩
- 二进制格式
- 并发传输
-
1.头部压缩
HTTP/2 会压缩头部(header),如果我们同时发送多个请求,并且头都是一样或者类似的,协议就会帮助我们消除重复的部分。
这也就是所谓的HPACK算法:在客户端和服务端同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样的字段了,只发送索引号,这样就提高速度了。2.二进制格式
HTTP/2 不像 HTTP/1.1 里的纯文本形式的报文,而是全面处采用了二进制格式,头信息和数据体都是二进制,统称为帧。
头信息帧
- 数据帧
虽然采用二进制格式对用户不友好,但是对计算机极度友好,因为计算机只懂二进制。当收到报文后,无需再将报文转换为二进制,而是直接解析二进制,这增加了数据传输的速率。
打个比方,在 HTTP/1.1 中,状态码 200 的二进制表示为 00110010 00110000 00110000,共用了三个字节;而在 HTTP/2 中,状态码 200 的二进制表示为 10001000,仅用了一个字节。也就是说,节省了 2 个字节。
3.并发传输
在 HTTP/1.1 中是基于请求-响应模型实现的。在同一个连接中,如果前一个请求没有完成,无法去处理下一个请求。当在发出请求后,迟迟无法接收到响应时,后续的请求因为无法被处理而累积,从而会造成队头阻塞的问题。
而 HTTP/2 就厉害了,其提出了 Stream 的概念,多条 Stream 复用同一条 TCP 连接。
从上图可以看到,一个 TCP 连接包含了多个Stream,Stream 里可以包含多个 Message,Message对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成。 Message 里包含一到多个 frame,Frame 是 HTTP/2 的最小单位,以二进制压缩格式存放 HTTP/1 的内容。
不同的 HTTP 请求用独一无二的 stream ID 来区分,接收端可以通过 stream ID 有序组装成 HTTP 消息,不同 stream 的帧可以乱序发送,因此可以并发不同的 stream,也就是 HTTP/2 可以并行交错地发送请求和响应。
比如下图,服务端并行交错地发送了两个响应: Stream 1 和 Stream 3,这两个 Stream 都是跑在一个 TCP 连接上,客户端收到后,会根据相同的 Stream ID 有序组装成 HTTP 消息。
4.服务器推送
HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。
客户端和服务器双方都可以建立 Stream, Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。
比如下图,Stream 1 是客户端向服务端请求的资源,属于客户端建立的 Stream,所以该 Stream 的 ID 是奇数(数字1);Stream 2 和 4 都是服务端主动向客户端推送的资源,属于服务端建立的 Stream,所以这两个 Stream 的 ID 是偶数(数字 2 和4)。
那究竟服务器什么时候会推送消息呢?
举个栗子,当我们请求一个 HTML 文件的时候,HTML 文件可以还需要依赖 CSS 文件来渲染,这个时候服务器可以直接主动推送 CSS 文件,从而减少了消息传递的次数。
💡HTTP/2 有什么缺陷?
HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,看似挺完美的了,然后还是会存在“队头阻塞”的问题,只不过问题出现在 TCP 这一层。
HTTP/2 是基于 TCP 协议来传输数据的,TCP 为字节流协议,即 TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当 前一个数据 没有到达时,后收到的数据只能存放在缓冲区里等待 该字节数据 到达,此时 HTTP/2 才能从内核中拿到数据,这就是 HTTP/2 的队头阻塞问题。
举个例子:
在图中,每个 packet 都有自己的序列号,而在发送过程中 packet 3 丢失了。此时即使 packet 4~6 到达了缓冲区,仍需要等待 packet 3 重传后,接收方的应用层才可以从内核中读取到数据。这是在 TCP 层面发生的。
所以,一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中所有的 HTTP 请求都必须等待这个丢了的包重传回来。
四、HTTP/3 做了哪些优化
在前面,我们知道了 HTTP/1.1 和 HTTP/2 均有队头阻塞:
- 在 HTTP/1.1 中,由于发送方可以发送多个请求,而接收方必须按顺序处理请求发送响应。当某个请求的响应过慢时,请求就会累积,造成队头阻塞。
- 在 HTTP/2 中,通过 Stream 复用解决了 HTTP/1.1 的队头阻塞问题,但是由于 TCP 协议的特性,即 TCP 层必须保证收到的字节流是统一完整的,当其中一个 Stream 丢失了,会阻塞其他所有的请求,TCP 层必须等待该 Stream 重传到达后才能被 HTTP 读取。这是发生在 TCP 层的队头阻塞。
HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!
UDP 发送不管顺序、也不管丢包,所以不会出现 HTTP/2 队头阻塞的问题。大家都知道 UDP 是不可靠运输的,但是基于 UDP 的 QUIC协议 可以实现类似 TCP 的可靠运输。
QUIC 有以下 3 个特点:
- 无队头阻塞
- 更快的连接建立
- 连接迁移
1.无队头阻塞
QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念。同时,QUIC 有自己的一套机制保证传输的可靠性。当某个流发生丢包时,只会阻塞这个流,其他流不会收到影响,因此不会存在队头阻塞的问题。这与 HTTP/2 不同,HTTP/2 只要某个流职工的数据包丢失了,其他流就会受到影响。
所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。2.更快的连接建立
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手。
HTTP/3 在传输数据前虽然需要 QUIC 协议握手,这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。
但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,如下图:
3.连接迁移
基于 TCP 传输协议的 HTTP 协议,是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。
那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
所以, QUIC 是一个在 UDP 之上的伪 TCP + TLS + HTTP/2 的多路复用的协议。
QUIC 是新协议,对于很多网络设备,根本不知道什么是 QUIC,只会当做 UDP,这样会出现新的问题,因为有的网络设备是会丢掉 UDP 包的,而 QUIC 是基于UDP 实现的,那么如果网络设备无法识别这个是 QUIC 包,那么就会当作 UDP包,然后被丢弃。
