一、先搞清楚 HTTP 是什么
HTTP(HyperText Transfer Protocol)就是浏览器和服务器之间"对话"的规则。你在浏览器输入一个网址,浏览器按照 HTTP 规则发一个请求,服务器按照 HTTP 规则回一个响应。
浏览器:「我要 /index.html」 → 请求(Request)
服务器:「给你,200 OK,内容如下...」 ← 响应(Response)
HTTP 从 1991 年诞生到现在,经历了 5 个大版本。每个版本都是为了解决上一个版本的痛点。
二、HTTP/0.9(1991)—— 最原始的版本
特点
- 只支持 GET 方法
- 没有请求头、没有响应头
- 只能传 HTML 文本
- 响应完就断开连接
一次对话长这样
请求:GET /hello.html
响应:<html>Hello World</html>
(连接断开)
就这么简单粗暴。没有状态码,没有 Content-Type,啥都没有。
存在的问题
- 只能传 HTML,不能传图片、CSS、JS
- 没有任何元数据(不知道内容多大、什么类型、什么编码)
- 每次请求都要新建连接
三、HTTP/1.0(1996)—— 有模有样了
解决了什么
| 新增能力 | 说明 |
|---|---|
| 请求头 & 响应头 | 可以携带元数据了(Content-Type、Content-Length 等) |
| 多种方法 | GET、POST、HEAD |
| 状态码 | 200、404、500 等,知道请求成功还是失败了 |
| Content-Type | 可以传图片、音频、视频,不再局限于 HTML |
| 版本号 | 请求行里带上 HTTP/1.0 |
一次对话长这样
请求:
GET /logo.png HTTP/1.0
Host: www.example.com
Accept: image/png
响应:
HTTP/1.0 200 OK
Content-Type: image/png
Content-Length: 4096
(图片二进制数据)
(连接断开)
存在的问题
最大的问题:短连接。 每个请求都要经历 TCP 三次握手 → 传数据 → 四次挥手。
一个网页有 1 个 HTML + 10 个图片 + 3 个 CSS + 5 个 JS = 19 个请求,就要建立 19 次 TCP 连接。
连接1:[三次握手] → GET /index.html → [响应] → [四次挥手]
连接2:[三次握手] → GET /style.css → [响应] → [四次挥手]
连接3:[三次握手] → GET /logo.png → [响应] → [四次挥手]
...重复 19 次
每次握手和挥手都要消耗时间和系统资源,极其浪费。
虽然有些实现支持非标准的 Connection: keep-alive,但这不是规范的一部分,行为不统一。
四、HTTP/1.1(1997)—— 用了二十多年的主力
解决了什么
1. 持久连接(Keep-Alive)—— 最重要的改进
默认开启 TCP 连接复用。一个连接可以发多个请求,不用每次都握手挥手。
HTTP/1.0:
连接1 → 请求1 → 响应1 → 断开
连接2 → 请求2 → 响应2 → 断开
连接3 → 请求3 → 响应3 → 断开
HTTP/1.1:
连接1 → 请求1 → 响应1 → 请求2 → 响应2 → 请求3 → 响应3 → 断开
2. 管线化(Pipelining)
理论上可以不等响应就发下一个请求:
客户端:请求1 → 请求2 → 请求3 →
等待...
服务端: ← 响应1 ← 响应2 ← 响应3
但实际几乎没人用(原因见下面的问题)。
3. 分块传输(Chunked Transfer)
不需要预先知道内容的总大小,边生成边发送:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
5\r\n
Hello\r\n
6\r\n
World\r\n
0\r\n
\r\n
适用场景:服务端流式输出(比如 ChatGPT 的逐字输出)。
4. 其他新增
| 能力 | 说明 |
|---|---|
| Host 头必选 | 一个 IP 可以托管多个域名(虚拟主机) |
| Cache-Control | 更精细的缓存控制(替代 Expires) |
| Range 请求 | 支持断点续传(下载了一半断了,可以接着下) |
| 100 Continue | 先问服务器"我要发一个大文件,你准备好了吗?" |
| PUT / DELETE / OPTIONS / PATCH | 更多的方法,RESTful API 的基础 |
存在的问题
问题一:队头阻塞(Head-of-Line Blocking)
这是 HTTP/1.1 最致命的问题。
虽然支持管线化,但响应必须按请求的顺序返回。如果第一个请求处理很慢,后面的请求即使已经处理完了,也必须排队等着。
请求顺序:请求1(慢查询)→ 请求2(静态图片)→ 请求3(CSS)
实际情况:
请求1 ████████████████████░░░░░(处理中...3秒)
请求2 ░░░░░░░░░░░░░░░░░░░████░ (早就好了,但必须等请求1)
请求3 ░░░░░░░░░░░░░░░░░░░░░░██ (也在排队等)
就像高速公路只有一条车道,前面的大卡车开得慢,后面的跑车再快也超不过去。
问题二:并发限制的妥协
为了绕开队头阻塞,浏览器的做法是 对同一个域名开 6 个并行 TCP 连接。
但这导致了新的问题:
- 每个连接都要三次握手 + TLS 握手,开销不小
- 服务器要维护大量连接
- 催生了"域名分片"等 hack 手段(把资源分散到 cdn1.xxx.com、cdn2.xxx.com 来突破 6 个限制)
问题三:头部冗余
每个请求都要带上完整的头部(Cookie、User-Agent、Accept 等),这些头部在同一个连接上几乎不变,但每次都要重复发送。一个大 Cookie 可能有 1-2KB,100 个请求就白白多传 100-200KB。
问题四:只能客户端主动
服务器不能主动给客户端推数据,只能客户端请求、服务器响应。想实现"服务器推送"只能用长轮询或 WebSocket。
五、HTTP/2(2015)—— 质的飞跃
核心思想
在一条 TCP 连接上实现真正的并行传输。
解决了什么
1. 二进制分帧(Binary Framing)—— 基础革新
HTTP/1.x 是文本协议(人能直接看懂),HTTP/2 改成了二进制协议。
所有数据被拆分成更小的 帧(Frame),每个帧有一个 Stream ID,标记它属于哪个请求/响应。
HTTP/1.1(文本):
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
\r\n
HTTP/2(二进制帧):
┌──────────┬───────────┬──────────────┐
│ Length:9 │ Type:HEAD │ Stream ID: 1 │ HEADERS 帧
├──────────┴───────────┴──────────────┤
│ :method = GET, :path = /index.html │
└─────────────────────────────────────┘
2. 多路复用(Multiplexing)—— 最重要的改进
多个请求/响应可以在同一个 TCP 连接上交错传输,互不阻塞。
HTTP/1.1:6 条连接,每条排队
连接1: [请求1 ████████████]
连接2: [请求2 ████]
连接3: [请求3 ██████]
连接4: [请求4 ███]
连接5: [请求5 ████████]
连接6: [请求6 ██]
HTTP/2:1 条连接,交错并行
连接1: [帧1a][帧2a][帧3a][帧1b][帧2b][帧3b][帧1c][帧2c]...
↑stream1 ↑stream2 ↑stream3 ↑stream1 ↑stream2
类比理解:
HTTP/1.1 就像有 6 条单行道,每条道上车要排队。HTTP/2 就像有一条超宽的高速公路,所有车可以同时跑,通过车牌号(Stream ID)区分。
这彻底解决了 HTTP 层的队头阻塞,也不再需要"域名分片"等 hack 手段。
3. 头部压缩(HPACK)
HTTP/2 用 HPACK 算法压缩头部:
- 维护一个静态表(61 个常见头部,如
:method: GET、content-type: text/html),用索引号代替完整字符串 - 维护一个动态表,记录当前连接用过的头部,后续只发索引号
- 对值做 Huffman 编码 压缩
第一次请求:
Cookie: session=abc123def456... (完整发送,同时存入动态表,索引 62)
第二次请求:
62 (只发一个索引号,省掉了几百字节)
效果:头部大小减少 85-95%。
4. 服务器推送(Server Push)
服务器可以主动推送客户端可能需要的资源。
客户端请求 /index.html
服务器响应 /index.html
服务器主动推送 /style.css ← 不用等客户端解析 HTML 后再请求
服务器主动推送 /app.js ← 提前到达,减少等待
但实际效果争议较大——很难准确预测客户端需要什么,推错了反而浪费带宽。Chrome 已在 2022 年移除了对 Server Push 的支持。
5. 流优先级(Stream Priority)
可以给不同的请求设置优先级。比如 CSS 优先级高于图片,因为 CSS 会阻塞渲染。
存在的问题
TCP 层的队头阻塞 —— HTTP/2 的阿喀琉斯之踵
HTTP/2 解决了 HTTP 层的队头阻塞,但底下的 TCP 层还有队头阻塞。
TCP 保证数据按序交付。如果一个 TCP 包丢失了,即使后面的包已经到达,TCP 也不会把它们交给应用层,而是等丢失的包重传回来。
TCP 传输:包1 → 包2(丢了!) → 包3 → 包4 → 包5
TCP 层:包1 ✓ 包2 ?等重传... 包3-5 已到但不能用
└── 所有 Stream 都被阻塞!
HTTP/1.1 开 6 个连接,一个连接丢包只影响那一个连接上的请求。HTTP/2 所有请求共用一个连接,一个包丢失会阻塞所有请求。
在网络质量差(丢包率 > 2%)的环境下,HTTP/2 的表现可能反而不如 HTTP/1.1。
TLS 握手开销
HTTP/2 虽然协议本身不强制加密,但所有浏览器都要求 HTTP/2 必须走 HTTPS。TLS 握手需要额外的 1-2 个 RTT。
六、HTTP/3(2022)—— 换掉了 TCP
核心思想
既然 TCP 的队头阻塞无法在 TCP 层面解决,那就 不用 TCP 了,改用 QUIC(基于 UDP)。
QUIC 是什么
QUIC(Quick UDP Internet Connections)是 Google 设计的传输层协议,跑在 UDP 上。你可以把它理解为"重新实现了一个更好的 TCP"。
HTTP/2 的协议栈: HTTP/3 的协议栈:
┌──────────┐ ┌──────────┐
│ HTTP/2 │ │ HTTP/3 │
├──────────┤ ├──────────┤
│ TLS │ │ QUIC │ ← 把 TLS 融合进来了
├──────────┤ ├──────────┤
│ TCP │ │ UDP │
├──────────┤ ├──────────┤
│ IP │ │ IP │
└──────────┘ └──────────┘
解决了什么
1. 彻底消灭队头阻塞
QUIC 在传输层就支持多路复用。每个 Stream 独立管理自己的数据包顺序,一个 Stream 丢包不影响其他 Stream。
HTTP/2 + TCP:
Stream 1 的包丢了 → 所有 Stream 被阻塞等重传
HTTP/3 + QUIC:
Stream 1 的包丢了 → 只有 Stream 1 等重传,Stream 2/3/4 正常收发
2. 0-RTT 连接建立
TCP + TLS 需要的握手:
TCP 三次握手: 1 RTT(客户端→服务器→客户端)
TLS 1.2 握手: 2 RTT
TLS 1.3 握手: 1 RTT
──────────────────
总计:TCP + TLS 1.3 = 2 RTT(数据才能开始传)
QUIC 的握手:
首次连接:1 RTT(QUIC 把传输层握手和加密握手合并了)
重连(0-RTT):0 RTT(第一个包就可以带数据!)
类比理解:
TCP + TLS 就像打电话:先拨号等接通(TCP),再输密码验证身份(TLS),然后才能说话。QUIC 就像发微信:直接把消息和身份验证一起发出去,对方收到就能回。重连时更像对方还认识你,直接开聊。
3. 连接迁移
TCP 连接用"源IP + 源端口 + 目标IP + 目标端口"四元组标识。你手机从 WiFi 切到 4G,IP 变了,TCP 连接就断了,必须重新握手。
QUIC 用一个 Connection ID 标识连接。IP 变了没关系,只要 Connection ID 不变,连接就能无缝切换。
TCP:WiFi → 4G = 连接断开 → 重新三次握手 + TLS 握手 → 恢复
QUIC:WiFi → 4G = IP 变了 → Connection ID 没变 → 无缝继续
对移动端体验提升巨大(电梯、地铁、从室内走到室外)。
4. 改进的头部压缩(QPACK)
HPACK 依赖严格的包顺序(因为动态表需要同步更新),和 QUIC 的乱序特性冲突。QPACK 解决了这个问题,允许在乱序到达的情况下也能正确解压头部。
存在的问题
| 问题 | 说明 |
|---|---|
| UDP 被运营商/防火墙限制 | 部分网络环境会限速或丢弃 UDP 包,需要回退到 TCP |
| 中间设备不友好 | 很多老旧的路由器、防火墙对 UDP 支持不好 |
| CPU 开销 | QUIC 在用户态实现(不在内核里),加解密和拥塞控制消耗更多 CPU |
| 生态还在成熟 | 服务端支持(Nginx 2022 才正式支持)、调试工具都还在完善 |
| 无法利用 TCP 的内核优化 | TCP 经过几十年优化,内核的 TCP 栈非常高效;QUIC 在用户态,暂时没法比 |
七、各版本一张图对比
HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/3
年份 1991 1996 1997 2015 2022
传输层 TCP TCP TCP TCP UDP(QUIC)
连接方式 短连接 短连接 持久连接 多路复用 多路复用
并发能力 无 无 管线化(废了) Stream并行 Stream并行
头部格式 无 文本 文本 HPACK压缩 QPACK压缩
加密 无 可选 可选 事实强制 强制(内置TLS)
队头阻塞 - 有 有(HTTP层) 有(TCP层) 无!
握手RTT 1(TCP) 1(TCP) 1(TCP) 2-3(TCP+TLS) 0-1(QUIC)
服务器推 无 无 无 支持(已废弃) 无
八、HTTPS 详解
8.1 为什么需要 HTTPS?
HTTP 是明文传输,存在三大安全风险:
| 风险 | 场景 | 后果 |
|---|---|---|
| 窃听 | 连公共 WiFi 时,路由器能看到所有内容 | 密码、银行卡号泄露 |
| 篡改 | 运营商在网页中插入广告 | 页面被注入恶意代码 |
| 冒充 | 钓鱼网站伪装成银行 | 用户被骗输入密码 |
HTTPS 就是 HTTP + TLS,用加密解决这三个问题:
| 安全需求 | 解决方案 |
|---|---|
| 防窃听 | 对称加密(AES),加密传输内容 |
| 防篡改 | 消息认证码(MAC),验证数据完整性 |
| 防冒充 | 数字证书 + 非对称加密(RSA/ECDSA),验证服务器身份 |
8.2 对称加密 vs 非对称加密
理解 HTTPS 之前,必须搞清楚这两种加密方式。
对称加密
加密和解密用同一把钥匙。
明文 "Hello" + 密钥 K → 加密 → 密文 "x7$f2"
密文 "x7$f2" + 密钥 K → 解密 → 明文 "Hello"
- 优点:速度快(AES 可达 GB/s 级别)
- 缺点:密钥怎么安全地传给对方?如果密钥被截获,加密就白搭
常见算法:AES、ChaCha20
非对称加密
有两把钥匙:公钥(公开)和 私钥(保密)。公钥加密的只有私钥能解,私钥加密的只有公钥能解。
公钥加密:明文 + 公钥 → 密文(只有私钥能解)
私钥签名:数据 + 私钥 → 签名(公钥可验证)
- 优点:不需要传递私钥,天然解决密钥分发问题
- 缺点:速度极慢(比对称加密慢 100-1000 倍)
常见算法:RSA、ECDSA、Ed25519
HTTPS 的选择:混合加密
既然对称加密快但密钥分发难,非对称加密安全但太慢,那就结合使用:
① 用非对称加密安全地交换一个"临时密钥"(只需要一次,慢就慢吧)
② 之后所有数据都用这个临时密钥做对称加密(快!)
8.3 TLS 握手流程(TLS 1.2)
这是 HTTPS 最核心的部分——建立安全连接的过程。
客户端 服务器
│ │
│ ① ClientHello │
│ - 支持的 TLS 版本 │
│ - 支持的加密套件列表 │
│ - 客户端随机数(Client Random) │
│ ─────────────────────────────────────────────────► │
│ │
│ ② ServerHello │
│ - 选定的 TLS 版本 │
│ - 选定的加密套件 │
│ - 服务器随机数(Server Random) │
│ ③ 服务器证书 │
│ ④ ServerHelloDone │
│ ◄───────────────────────────────────────────────── │
│ │
│ ⑤ 验证证书(证书链 → 根证书) │
│ ⑥ 生成预主密钥(Pre-Master Secret) │
│ ⑦ 用服务器公钥加密预主密钥 │
│ ⑧ ClientKeyExchange(发送加密后的预主密钥) │
│ ⑨ ChangeCipherSpec("我准备好了,之后都加密") │
│ ⑩ Finished(第一条加密消息) │
│ ─────────────────────────────────────────────────► │
│ │
│ ⑪ 用私钥解密得到预主密钥 │
│ ⑫ 双方各自计算会话密钥 │
│ = f(Client Random + │
│ Server Random + │
│ Pre-Master Secret) │
│ ⑬ ChangeCipherSpec │
│ ⑭ Finished │
│ ◄───────────────────────────────────────────────── │
│ │
│ ═══════ 之后所有数据用会话密钥做对称加密 ═══════════ │
需要 2 个 RTT 才能开始传数据。
为什么要用三个随机数?
最终的会话密钥由三个随机数共同生成:
会话密钥 = PRF(Pre-Master Secret, Client Random, Server Random)
- Client Random:防止服务器重放攻击
- Server Random:防止客户端重放攻击
- Pre-Master Secret:只有双方知道的秘密(通过非对称加密安全交换)
三个随机数混合,即使其中一个被猜到,也无法推导出会话密钥。
8.4 TLS 1.3(2018)—— 更快更安全
TLS 1.3 是对 TLS 1.2 的大幅简化和优化。
主要改进
| 改进 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手 RTT | 2 RTT | 1 RTT(首次),0 RTT(重连) |
| 密钥交换 | RSA 或 ECDHE | 只支持 ECDHE(前向安全) |
| 加密套件 | 几十种 | 精简到 5 种 |
| 废弃的算法 | - | 砍掉 RC4、DES、3DES、MD5、SHA-1 等不安全算法 |
| 握手加密 | 握手过程明文 | 握手消息也加密(ServerHello 之后) |
TLS 1.3 握手流程(只需 1 RTT)
客户端 服务器
│ │
│ ClientHello │
│ - 客户端随机数 │
│ - 支持的加密套件 │
│ - 客户端的 ECDHE 公钥 ← 关键!直接带上了 │
│ ─────────────────────────────────────────────────► │
│ │
│ ServerHello │
│ + 服务器的 ECDHE 公钥 │
│ + 证书 │
│ + Finished │
│ ◄───────────────────────────────────────────────── │
│ │
│ 双方用 ECDHE 算出相同的密钥 │
│ Finished │
│ ─────────────────────────────────────────────────► │
│ │
│ ═══════ 1 RTT 后就可以传数据了 ═══════════ │
为什么快了? 因为 TLS 1.2 客户端要等拿到服务器证书和公钥后才能开始密钥交换,而 TLS 1.3 客户端直接在 ClientHello 里就把自己的 ECDHE 公钥发出去了(赌服务器会接受),省了一个来回。
0-RTT 重连
如果之前连过这个服务器,客户端缓存了一个 PSK(Pre-Shared Key):
客户端:ClientHello + PSK + 加密的应用数据 →
服务器直接解密处理
第一个包就能带业务数据,延迟降到极致。
安全代价: 0-RTT 数据没有前向安全性,而且可能被重放攻击。所以只适合幂等请求(GET),不适合会产生副作用的操作(POST 转账)。
8.5 数字证书:怎么证明"你是你"
问题
非对称加密解决了"传密钥"的问题,但引入了新问题:你怎么知道拿到的公钥是真的?
中间人攻击:
客户端 ←→ 中间人(伪装成服务器) ←→ 真正的服务器
中间人把自己的公钥发给客户端
客户端以为这是服务器的公钥,用它加密数据
中间人解密 → 看到明文 → 用服务器真正的公钥重新加密 → 发给服务器
解决:数字证书 + CA
引入一个双方都信任的第三方 —— CA(Certificate Authority,证书颁发机构)。
① 服务器生成公私钥对
② 服务器把公钥 + 域名信息提交给 CA
③ CA 验证服务器确实拥有这个域名
④ CA 用自己的私钥对"服务器公钥 + 域名信息"做数字签名
⑤ CA 颁发证书(包含:服务器公钥 + 域名 + CA 签名 + 有效期等)
⑥ 客户端收到证书后,用 CA 的公钥验证签名
⑦ 签名正确 → 公钥可信 → 建立加密连接
类比理解:
就像你去政务大厅办事,需要身份证(证书)。身份证是公安局(CA)发的,上面有你的照片(公钥)和钢印(CA 签名)。办事员通过钢印确认身份证是真的,从而相信你就是本人。
证书链
CA 的公钥又是谁来保证的?答案是证书链:
根证书(Root CA) ← 预装在操作系统/浏览器中,无条件信任
│
└── 中间证书(Intermediate CA) ← 根 CA 签发的
│
└── 服务器证书(End Entity) ← 中间 CA 签发的
验证过程从下往上:
- 用中间 CA 的公钥验证服务器证书的签名
- 用根 CA 的公钥验证中间 CA 证书的签名
- 根 CA 证书在本地受信任列表中 → 整条链可信
为什么要分层?如果根 CA 直接签发所有证书,一旦根 CA 私钥泄露,后果不堪设想。分层后,即使中间 CA 出问题,只需要吊销这个中间 CA,不影响其他。
8.6 前向安全(Forward Secrecy)
问题
如果用 RSA 做密钥交换(TLS 1.2 的一种模式),一旦服务器私钥泄露,攻击者可以:
- 解密之前录制的所有流量中的 Pre-Master Secret
- 从而推导出所有历史会话密钥
- 所有历史通信全部暴露
解决:ECDHE 密钥交换
每次连接都生成临时的(Ephemeral) 公私钥对,用完即销毁。
连接1:临时密钥对 A → 会话密钥 X → 销毁临时密钥对 A
连接2:临时密钥对 B → 会话密钥 Y → 销毁临时密钥对 B
连接3:临时密钥对 C → 会话密钥 Z → 销毁临时密钥对 C
即使服务器的长期私钥泄露,也无法解密之前的通信,因为临时密钥已经不存在了。
这就是"前向安全"(Forward Secrecy)。TLS 1.3 强制要求使用 ECDHE,所有连接都具有前向安全性。
8.7 HTTPS 的性能影响
| 开销 | 说明 | 缓解方案 |
|---|---|---|
| 握手延迟 | TLS 1.2 多 2 RTT,TLS 1.3 多 1 RTT | 升级 TLS 1.3、会话复用、0-RTT |
| 加解密 CPU | AES 加解密消耗 CPU | 现代 CPU 都有 AES-NI 硬件加速,开销几乎可忽略 |
| 证书传输 | 证书链可能 3-5KB | OCSP Stapling 减少验证开销 |
| 内存 | 每个连接需要维护加密上下文 | 影响很小 |
现代环境下 HTTPS 的额外开销已经非常小了,Google 的数据显示 HTTPS 只增加了不到 2% 的 CPU 负载和不到 10ms 的延迟。
8.8 常见 HTTPS 相关概念
| 概念 | 解释 |
|---|---|
| HSTS | 服务器告诉浏览器"以后只用 HTTPS 访问我",防止降级攻击 |
| OCSP Stapling | 服务器主动把证书有效性证明发给客户端,省去客户端向 CA 查询 |
| CT(Certificate Transparency) | 所有颁发的证书必须公开记录,防止 CA 偷偷签发恶意证书 |
| SNI | 客户端在 TLS 握手时告诉服务器要访问哪个域名(一个 IP 上多个 HTTPS 站点) |
| ESNI / ECH | 加密 SNI,防止中间人知道你在访问哪个网站 |
| 证书固定(Pinning) | App 内置预期的证书指纹,防止中间人使用合法但非预期的证书 |
| Let's Encrypt | 免费、自动化的 CA,推动了 HTTPS 的全面普及 |
| 双向认证(mTLS) | 不仅服务器要证书,客户端也要证书(常见于企业内部、金融系统) |
九、一张图总结
1991 HTTP/0.9 ─── 只能传 HTML,连个状态码都没有
│
1996 HTTP/1.0 ─── 有了头部、状态码、多媒体,但每次请求都要新建连接
│
1997 HTTP/1.1 ─── 持久连接、缓存控制、断点续传,但有队头阻塞
│ └── HTTPS(TLS 1.0~1.2) 解决安全问题
│
2015 HTTP/2 ──── 二进制分帧、多路复用、头部压缩,但 TCP 层仍有队头阻塞
│ └── TLS 1.3:1-RTT 握手、前向安全
│
2022 HTTP/3 ──── 换用 QUIC(UDP),彻底消灭队头阻塞,0-RTT 连接,连接迁移
每一代都在解决上一代留下的问题,同时也带来了新的挑战。技术演进就是这样一步步往前走的。