HTTP/1.0 就像是“一问一答”的电话,每次打电话(请求)都得先拨号(建立连接),说完一句话(发送数据)就挂断(关闭连接),再打下一通电话。效率比较低。
HTTP/2.0 就像是“多路复用”的电话会议,一次拨号(建立连接)后,大家可以在同一个会议室里同时说多句话(多路复用),而且还可以压缩语言(头部压缩),甚至会议主持人(服务器)可以提前把大家可能需要的文件准备好(服务器推送)。这样效率就高多了。
为什么需要 HTTP?
在理解 HTTP/1.0 和 HTTP/2.0 的区别之前,我们得先明白 HTTP 协议本身是干嘛的。
想象一下,互联网就像一个巨大的图书馆。你(客户端)想从图书馆里借一本书(资源),图书馆管理员(服务器)需要知道你要哪本书,然后把书给你。HTTP(HyperText Transfer Protocol,超文本传输协议)就是你和图书馆管理员之间交流的“语言”或“规矩”。它规定了你如何提出请求,管理员如何回应,以及数据如何传输。
核心目标: 让客户端和服务器能够高效、可靠地交换信息(主要是网页、图片、视频等资源)。
从 1.0 到 2.0
HTTP/1.0:初期的“一问一答”模式
HTTP/1.0 是互联网早期设计的协议,它非常简单直接,就像我们上面说的“一问一答”的电话。
核心思想: 每次请求-响应都建立一个新的 TCP 连接,完成后立即关闭。
- 用户需求: 我要一个网页。
- 网页构成: 一个网页通常不只包含 HTML 文本,还有图片、CSS 文件、JavaScript 文件等等。
- 1.0 的做法:
- 客户端请求 HTML 文件。
- 服务器响应 HTML 文件。
- 连接关闭。
- 客户端解析 HTML,发现还需要图片 A。
- 客户端再次建立 TCP 连接,请求图片 A。
- 服务器响应图片 A。
- 连接关闭。
- …以此类推,直到所有资源都加载完毕。
这种模式有什么缺点?
- 连接建立/关闭开销大: 每次建立 TCP 连接都需要“三次握手”,关闭需要“四次挥手”,这就像每次打电话都要先拨号、等待接通、再挂断,非常耗时。
- 队头阻塞(Head-of-Line Blocking): 即使你有很多请求要发,也必须等前一个请求的响应完全回来,才能发送下一个请求。这就像你在排队买票,前面的人没买完,你就不能买。
- 带宽利用率低: 连接频繁建立和关闭,导致网络带宽无法持续高效利用。
流程图:HTTP/1.0 请求流程
HTTP/1.1:小修小补,引入持久连接
HTTP/1.1 在 1.0 的基础上做了一些改进,最核心的就是引入了持久连接(Persistent Connections),也叫 Keep-Alive。
默认情况下,一个 TCP 连接在发送完一个请求-响应后不会立即关闭,而是保持一段时间,允许在这个连接上发送后续的请求。
- 1.0 的痛点: 频繁建立/关闭连接。
- 如何优化? 既然一个网页需要多个资源,那能不能只建立一次连接,然后在这个连接上把所有资源都请求完再关闭呢?
- 1.1 的做法:
- 客户端请求 HTML 文件。
- 建立 TCP 连接。
- 发送 HTTP 请求 (HTML)。
- 服务器响应 HTML。
- 连接保持开放。
- 客户端解析 HTML,发现需要图片 A。
- 在同一个 TCP 连接上,发送 HTTP 请求 (图片 A)。
- 服务器响应图片 A。
- 连接保持开放。
- …直到所有资源都加载完毕,或者达到超时时间,连接才关闭。
1.1 解决了连接建立/关闭的开销,但还有什么问题?
- 队头阻塞依然存在: 虽然连接是持久的,但请求和响应仍然是串行的。你必须等前一个请求的响应完全回来,才能发送下一个请求。这就像你和朋友打电话,虽然没挂断,但你们还是得轮流说话,不能同时说。
- 头部冗余: 每次请求都会发送大量的重复头部信息(如 User-Agent, Accept 等),浪费带宽。
- 没有服务器推送: 服务器只能被动响应,不能主动推送客户端可能需要的资源。
流程图:HTTP/1.1 请求流程 (持久连接)
HTTP/2.0:彻底的性能革命
随着网页越来越复杂,资源越来越多,HTTP/1.1 的队头阻塞问题变得越来越突出。人们开始思考,有没有一种方式,能让一个连接同时处理多个请求和响应,就像多车道高速公路一样?这就是 HTTP/2.0 的核心思想。
HTTP/2.0 基于 Google 的 SPDY 协议,它在应用层和传输层之间增加了一个二进制分帧层。
多路复用(Multiplexing): 在一个 TCP 连接上,同时发送多个请求和接收多个响应,且请求和响应之间互不影响。
二进制分帧(Binary Framing): 所有通信都被分解为更小的、独立的帧,并以二进制格式传输。
头部压缩(Header Compression): 使用 HPACK 算法压缩 HTTP 头部,减少冗余数据传输。
服务器推送(Server Push): 服务器可以在客户端请求某个资源时,主动推送客户端可能需要的其他资源。
请求优先级(Request Prioritization): 客户端可以为请求设置优先级,服务器可以根据优先级决定响应顺序。
1.1 的痛点: 队头阻塞,头部冗余,无服务器推送。
如何解决队头阻塞?
- 多路复用: 把每个请求和响应都拆分成小块(帧),给每个帧一个唯一的标识符。然后这些帧可以在同一个 TCP 连接上乱序发送,接收方根据标识符再重新组装。这就像快递公司,把你的包裹拆成小件,然后和别人的小件一起装车,到了目的地再根据单号重新组装。
- 二进制分帧: 为什么是二进制?因为二进制解析效率高,更紧凑,不像文本协议那样需要复杂的解析。
如何解决头部冗余?
- 头部压缩: 很多请求的头部信息是重复的,比如 User-Agent。我们可以维护一个“字典”(索引表),把常用的头部信息存起来,下次只发送字典的索引号就行了。
如何提高加载速度?
- 服务器推送: 客户端请求 HTML 页面时,服务器知道这个页面肯定需要 CSS 和 JS 文件,那服务器就可以在客户端还没请求 CSS 和 JS 之前,就把它们“推”给客户端。这样客户端就不用再发请求了,节省了往返时间。
如何优化资源加载顺序?
- 请求优先级: 客户端可以告诉服务器,哪个资源更重要(比如 CSS 比图片更重要),服务器就可以优先处理重要的请求。
流程图:HTTP/2.0 请求流程
核心本质
- HTTP/1.0: 简单粗暴,每次任务独立完成。它的本质是串行处理,资源利用率低。
- HTTP/1.1: 在 1.0 基础上打了个补丁,通过持久连接减少了连接开销,但本质上还是串行请求-响应,只是在同一个管道里串行。
- HTTP/2.0: 彻底改变了传输方式,引入了多路复用。它的本质是并行处理,通过在应用层和传输层之间增加一个“调度层”(二进制分帧层),将逻辑上的多个流映射到物理上的一个 TCP 连接,从而解决了队头阻塞,并引入了更多优化手段。它不再是简单的“一问一答”,而是更像一个智能的“数据管道”。
继续思考
- 为什么不直接在 TCP 层解决队头阻塞? TCP 层的队头阻塞是数据包丢失重传导致的。如果一个 TCP 包丢失了,即使后面的包都收到了,TCP 也必须等待丢失的包重传成功并按序组装,才能把数据交给应用层。HTTP/2.0 的多路复用是在应用层实现的,它解决了 HTTP 层面(逻辑流)的队头阻塞,但无法解决 TCP 层面(物理包)的队头阻塞。这也是为什么 HTTP/3.0 转向 UDP (QUIC) 的原因之一,因为它想从传输层彻底解决队头阻塞。
- HTTP/2.0 的安全性: 虽然 HTTP/2.0 协议本身不强制加密,但几乎所有主流浏览器都只支持基于 TLS/SSL 的 HTTP/2.0 (即 HTTPS)。这使得 HTTP/2.0 在实践中比 HTTP/1.x 更安全。
- HTTP/2.0 的适用场景: 对于需要加载大量小文件、或者需要频繁与服务器交互的单页应用 (SPA) 等场景,HTTP/2.0 的性能优势尤为明显。
队头阻塞的本质是:在需要保证顺序性的系统中,如果“队头”的元素处理受阻,那么“队尾”的元素即使已经准备好,也无法越过队头被处理。
TCP 队头阻塞是为了保证数据包的可靠性和按序交付。如果一个数据包丢失,后续的数据包即使到达,也无法被应用层消费,因为 TCP 无法确定后续数据包的完整上下文。
HTTP/1.x 队头阻塞是因为 HTTP/1.x 协议设计上,在单个 TCP 连接中,请求和响应是严格串行且一一对应的。它没有机制来区分和并行处理不同的逻辑流。
流程图:HTTP/2.0 多路复用与流
为什么 HTTP/2.0 能解决 HTTP 层的队头阻塞?
HTTP/2.0 引入了多路复用。它把每个 HTTP 请求和响应都看作一个独立的“流”(Stream),每个流都有自己的 ID。这些流的数据被拆分成更小的“帧”,这些帧可以在同一个 TCP 连接上乱序发送。接收方根据帧的 ID 重新组装成完整的流。HTTP/2.0 解决了应用层(HTTP 协议层面)的队头阻塞,因为它不再强制请求和响应的串行顺序。
HTTP/2.0 为什么不能解决 TCP 层的队头阻塞?
HTTP/2.0 仍然是基于 TCP 协议的。如果底层的 TCP 连接中,某个数据包丢失了,那么整个 TCP 连接仍然会因为等待这个丢失的数据包重传而暂停,这会影响到所有在当前 TCP 连接上跑的 HTTP/2.0 流。这就像电话会议(HTTP/2.0)开得很好,但如果电话线(TCP 连接)断了,所有人都受影响。
HTTP/3.0 如何解决 TCP 层的队头阻塞?
HTTP/3.0 放弃了 TCP,转而使用基于 UDP 的 QUIC 协议。QUIC 协议在传输层实现了自己的可靠传输和多路复用机制。它为每个逻辑流分配独立的序列号,这样即使一个流的数据包丢失,也只会影响到这一个流,而不会阻塞其他流的数据传输。这就像,你和朋友们在不同的房间里打电话,即使一个房间的电话线断了,其他房间的通话也不受影响。
特性 | HTTP/1.0 | HTTP/2.0 |
---|---|---|
连接管理 | 短连接:每个请求-响应建立新 TCP 连接,完成后立即关闭。 | 长连接/多路复用:一个 TCP 连接可同时处理多个请求和响应。 |
请求方式 | 串行请求:必须等待前一个响应完成后才能发送下一个请求。 | 并行请求:多个请求可同时发送,响应可乱序返回。 |
队头阻塞 | 存在:HTTP 层面和 TCP 层面都存在。 | 解决 HTTP 层面:通过多路复用解决。TCP 层面仍可能存在。 |
数据传输 | 文本协议:基于文本,解析相对复杂。 | 二进制分帧:所有数据分解为二进制帧传输,解析高效。 |
头部处理 | 无压缩:每次请求发送完整且重复的头部信息。 | 头部压缩 (HPACK):压缩头部,减少冗余数据。 |
服务器推送 | 不支持:服务器只能被动响应。 | 支持:服务器可主动推送客户端可能需要的资源。 |
请求优先级 | 不支持 | 支持:客户端可设置请求优先级。 |
安全性 | 通常不加密 (HTTP) | 多数实现强制加密 (HTTPS) |
性能 | 较低,尤其在加载大量资源时。 | 显著提高,尤其在加载大量资源时。 |
HTTP/2.0 是对 HTTP/1.x 的一次彻底的性能优化 对于HTTP/1.x,在服务器处理某个请求资源回复慢时,会阻塞其他请求,而2.0使用流的逻辑标识,可以同时发不同的请求,尽管带宽是一样的,却可以降低延迟,更高效率的利用网络传输通道,它将TCP的流组合成不同的请求数据,解决了应用层的队头阻塞,可是依旧是基于TCP流的,这意味,由于网络层面的TCP出错依旧会队头阻塞,并没有解决本质问题
而3.0彻底解决了这个问题,3.0不再使用TCP而是UDP,每个请求都是额外的流,QUIC协议可以将传输和加密的握手结合在一起,如果客户端之前连接过服务器,并且服务器支持,甚至可以实现 0-RTT 握手,即客户端在发送第一个数据包时就包含应用数据,几乎没有延迟。 TCP 连接是基于 IP 地址和端口号的。如果你的设备从 Wi-Fi 切换到移动数据(IP 地址变化),TCP 连接就会断开,需要重新建立。 QUIC 连接是基于一个 64 位的连接 ID。即使客户端的 IP 地址或端口号发生变化,只要连接 ID 不变,QUIC 连接就可以保持活跃,无需重新建立。
流程图:HTTP/3.0 (QUIC) 解决队头阻塞
UDP本身并不会有失败重发的机制,所以即使是一个UDP连接网络抖动不会影响整个UDP连接的数据重发,而是在QUIC协议负责每个流的重发,QUIC实现都是在应用层的,也因此对操作系统内核没有额外要求,不需要更改,把TCP的可靠性实现由操作系统内核上升到应用层去实现这些