HTTP1.0
HTTP/1.0 的设计初衷只是为了在网络上传输 HTML 文档。
模型:Request-Response(一问一答)。
底层:基于 TCP。
瓶颈:TCP 连接成本过高。
在 1.0 默认行为中,每一次 HTTP 请求(比如下载一张图片)都要新建一个 TCP 连接。
TCP 三次握手:这意味着每个请求都要先消耗 1.5 个 RTT(往返时延)才能开始发数据。
TCP 慢启动(Slow Start):新连接的速度是从低到高爬坡的,短连接导致 TCP 永远在低速爬坡阶段就断开了,带宽利用率极低。
HTTP1.1
为了解决连接成本问题,HTTP/1.1 在 1999 年成为了标准,并统治互联网长达 15 年。
1. Keep-Alive(长连接):解决连接复用
- 机制:引入持久连接(Persistent Connection)。默认
Connection: keep-alive。 - 效果:只要连接不断开,后续请求可以直接发送,省去了三次握手和 TCP 慢启动的开销。
- 细节:设置
Keep-Alive: timeout=5, max=100来控制连接的生命周期。
2. Pipeline(管道化):解决串行等待(失败的尝试)
- 尝试:允许客户端一口气发出去 10 个请求,不用等第一个回来再发第二个。
- 致命缺陷:HTTP 层面的队头阻塞(HOL
Blocking)。
- 服务器必须按照接收请求的顺序返回响应(FIFO)。如果第 1 个请求处理很慢(比如数据库查询),第 2-10 个请求即使处理完了,也必须排队等待第 1 个发送完毕。
- 由于这个风险,浏览器厂商默认都关闭了 Pipeline。
3. Transfer-Encoding: chunked(分块传输)
- 痛点:HTTP/1.0 需要在 Header 里通过
Content-Length告诉对方包体多大。如果是动态生成的流(比如视频、动态页面),服务器必须先把所有数据生成完算出长度才能发,延迟太高。 - 解决:
chunked模式允许服务器产生一部分数据就发一部分,最后发一个长度为 0 的块表示结束。这使得流式传输成为可能。
4. 缓存控制增强
- 引入
Cache-Control、Etag、If-None-Match,比 1.0 的Expires和Last-Modified提供了更精细的缓存策略,节省带宽。
HTTP2
HTTP/1.1 虽然有了长连接,但本质上请求还是串行的。为了并发下载资源,浏览器被迫对同一个域名开启 6 个 TCP 连接。HTTP/2 旨在解决这个问题。
1. 核心改变:二进制分帧层 (Binary Framing Layer)
这是 HTTP/2 的基石。
- HTTP/1.1:纯文本,换行符分隔。解析慢,且无法交错。
- HTTP/2:将报文切分为更小的Frame(帧)。
- Headers Frame:存放头部。
- Data Frame:存放包体。
- 意义:机器解析二进制比解析文本快得多,更重要的是,这为多路复用提供了基础。
2. 多路复用 (Multiplexing):解决 HTTP 队头阻塞
- 机制:在一个 TCP 连接中,通过
Stream(流) 的概念,同时传输多个请求的数据帧。
- 每个帧都有一个
Stream ID。 - 比如:Stream 1 的帧和 Stream 2 的帧可以乱序发送,接收端根据 ID 重新组装。
- 每个帧都有一个
- 彻底解决:HTTP/1.1 的队头阻塞。请求 A 的处理慢,完全不影响请求 B 的数据传输。

3. HPACK:头部压缩
- 痛点:HTTP/1.1 的 Header 很多是冗余的(如 Cookie, User-Agent),每次都要重复发几百字节,浪费带宽。
- 原理:
- 静态表:预定义 61 个常用头部(如
method: GET对应索引 2)。传输时只传索引号(1 个字节)。 - 动态表:连接建立后,双方动态维护一个表。第一次发过
User-Agent后,把它存入动态表,下次只发索引即可。 - Huffman 编码:对字符串进行压缩。
- 静态表:预定义 61 个常用头部(如
TLS1.2
在进入 HTTP3 之前,先来阐述一下 TLS1.2 和 TLS1.3,它们都是 HTTPS 的基石。
HTTPS 常用的密钥交换算法有两种,分别是 RSA 和 ECDHE 算法。
其中,RSA 是比较传统的密钥交换算法,它不具备前向安全的性质,因此现在很少服务器使用的。而 ECDHE 算法具有前向安全,所以被广泛使用。
1. RSA
我们先回顾一下 RSA 方式的密钥交换,笔者发现大多数的书籍和文章介绍的都是这种方式。但事实上因为它不具备前向安全的性质,现在很少被服务器所使用。

- 客户端发送 ClientHello:客户端向服务器发送
ClientHello消息,包含支持的 TLS 版本、加密算法列表、压缩方法以及一个随机数(Client Random),用于后续生成会话密钥。 - 服务器发送 ServerHello:服务器收到
ClientHello后,选择一个 TLS 版本和加密算法,发送ServerHello消息,包含选择的版本、加密算法、另一个随机数(Server Random)以及服务器的数字证书(包含公钥和 CA 信息)。 - 客户端验证证书并生成预主密钥:
- 客户端验证服务器的数字证书,检查是否由受信任的 CA 颁发(一直验证到根 CA)、是否在有效期内、域名是否匹配。如果验证通过,客户端生成一个预主密钥(Pre-Master Secret),并用服务器的公钥加密后发送给服务器。
- 结合预主密钥,结合 Client Random 和 Server Random,生成对称加密的会话密钥(Session Key)。
- 并把迄今为止的通信数据内容生成一个摘要,形成
Finished报文,发送给服务端。
- 服务端生成会话密钥:
- 服务器使用私钥解密客户端发送的预主密钥,结合 Client Random 和 Server Random,用跟客户端相同的算法生成对称加密的会话密钥(Session Key)。
- 然后发送
Finished报文。
- 完成握手:如果双方都能正确解密对方的
Finished消息,握手成功。 - 加密传输数据:握手完成后,客户端和服务器使用会话密钥对所有通信数据进行加密和解密,确保数据的机密性和完整性。
2. ECDHE
向前安全的定义
即便现在私钥泄漏了,也无法破解过去截获的流量。
为什么说 TLS 1.2 使用 RSA 握手会有前向安全(Forward Secrecy)的问题呢?
在 TLS 1.2 (RSA) 中,最终用来加密数据的 Session Key 是这样算出来的:
\[ Session\ Key = Function(ClientRandom, ServerRandom, Pre\text{-}Master\ Secret) \]
因为 ClientRandom 和 ServerRandom 都是明文的,所以整个会话的安全性,完全变成了"如何保护 Pre-Master Secret"这一个问题。
只要私钥一旦泄露,那黑客就可以根据过完的 ClientRandom 和 ServerRandom 以及解密后的 pre-master-secret 还原所有的历史消息。
ECDHE 旨在解决这个问题。这个名字由四部分组成:
- EC (Elliptic Curve):椭圆曲线。相比 RSA,它用更短的密钥就能实现同等强度,计算更快,带宽更省。
- DH (Diffie-Hellman):密钥交换算法。它的魔力在于:双方只交换公钥,就能算出同一个密钥,而无需传递密钥本身。
- E (Ephemeral):临时(关键点!)。指每次握手生成的"私钥"都是临时的,用完即焚。
它的核心原理是:核心原理(离散对数难题)。
为了不陷入复杂的数学证明,我们用最简化的数学逻辑描述:
假设椭圆曲线上有一个基点 \(G\)。
- Client 生成一个随机临时私钥 \(a\),计算公钥 \(A = a \cdot G\),发给 Server。
- Server 生成一个随机临时私钥 \(b\),计算公钥 \(B = b \cdot G\),发给 Client。
- 计算共享密钥:
- Client 计算:\(S = a \cdot B = a \cdot b \cdot G\)
- Server 计算:\(S = b \cdot A = b \cdot a \cdot G\)
- 结果一样!双方都得到了 \(S\)。
注意:黑客截获了 \(A\) 和 \(B\),但根据椭圆曲线离散对数难题,他无法反推出 \(a\) 或 \(b\),因此算不出 \(S\)。
为什么 ECDHE 有前向安全?在 ECDHE 中,Session Key
依然需要三个参数,但第三个参数的来源变了:
- ClientRandom(明文,可见)。
- ServerRandom(明文,可见)。
- ECDH 算出的共享密钥(替代了原来的 Pre-Master Secret)。
关键区别在于:
- 这个共享密钥是 Client 和 Server 通过交换临时的(Ephemeral)公钥 \(A\) 和 \(B\) 算出来的。
- 在这个过程中,服务器的长期私钥仅仅用来"签名"(证明 \(B\) 是我发的),而不参与密钥的加密或计算。
- 握手结束后,Client 和 Server 会把计算用的临时私钥 \(a\) 和 \(b\) 删掉。
我们来看使用 ECDHE 的握手流程:

客户端发送 ClientHello: 客户端向服务器发送 ClientHello 消息,包含支持的 TLS 版本、加密算法列表(必须包含 ECDHE 算法)、压缩方法以及一个随机数(Client Random)。
服务器发送 ServerHello 和密钥协商参数: 服务器收到 ClientHello 后,选择 ECDHE 算法,发送 ServerHello(包含选择的版本、算法、Server Random)以及服务器数字证书。
关键区别:服务器会在本地生成一对临时的椭圆曲线公私钥。它将服务器临时公钥发送给客户端,并用证书中的长期私钥对这个临时公钥进行数字签名,以防止篡改。
客户端验证并协商预主密钥:
客户端验证服务器证书的合法性。并用证书中的长期公钥验证收到的服务器临时公钥的签名是否有效。
验证通过后,客户端也在本地生成一对临时的椭圆曲线公私钥。
计算预主密钥:客户端利用自己的临时私钥和收到的服务器临时公钥,通过 ECDH 算法在本地计算出预主密钥(Pre-Master Secret)。
客户端将自己的临时公钥明文发送给服务器。
结合算出的预主密钥、Client Random 和 Server Random,生成会话密钥(Session Key),并发送 Finished 报文。
服务端计算预主密钥:
- 计算预主密钥:服务器收到客户端的临时公钥后,利用自己的临时私钥和收到的客户端临时公钥,通过同样的 ECDH 算法在本地计算出一模一样的预主密钥。
- 结合预主密钥、Client Random 和 Server Random,生成相同的会话密钥(Session Key),并发送 Finished 报文。
完成握手: 如果双方都能正确解密对方的 Finished 消息,握手成功。双方立即删除各自生成的临时公私钥(实现前向安全)。
加密传输数据: 握手完成后,客户端和服务器使用会话密钥对通信数据进行加密传输。
TLS1.3
TLS 1.2 虽然支持 ECDHE,但握手还是很慢(需要 2-RTT),而且保留了很多不安全的加密算法。TLS 1.3 做了大刀阔斧的改革。
TLS 1.2 的逻辑是:"先打招呼,商量用什么算法,再交换密钥"。 TLS 1.3 的逻辑是:"我猜你会用这个算法,先把公钥给你!"
1-RTT

通过上述流程图可以看到,TLS1.3 直接从 2-RTT 缩短到了 1-RTT:
- Client 在发
ClientHello时,不再干等服务器选算法,而是直接猜测服务器支持 ECDHE,并直接把自己的Key Share(临时公钥) 一起发过去。 - Server 收到后直接生成密钥,回复
ServerHello+Finished。握手直接结束。
0-RTT
更进一步,如果是连接恢复的情况,TLS1.3 还可以做到 0-RTT。
在初始连接 1-RTT
之后,在连接结束前,服务器会生成一个极其重要的数据结构,叫做
Session Ticket(会话票据),并通过
NewSessionTicket 指令发给客户端。Session
Ticket 包含了 Pre-Shared Key(预共享密钥)
- 一个 PSK (Pre-Shared Key,预共享密钥):这是从当前的会话主密钥衍生出来的一个秘密数据。
- 有效期、加密算法等信息。
- 这个 Ticket 通常只有服务器能解密(用服务器只有自己知道的密钥加密)。
客户端收到 Ticket 后,把它安全地存在本地(比如浏览器的缓存里),然后断开连接。
假设过了几个小时,用户又打开了同一个网站。客户端发现本地有上次存的 Ticket。
客户端动作:
- 取出 PSK:客户端从 Ticket 里提取出 PSK。
- 生成早期密钥:客户端利用这个 PSK,结合当前的 Client Random,提前算出用于加密早期数据(Early Data)的密钥。
- 客户端构造一个超级数据包,一次性发给服务器:
- ClientHello:标准的问候,包含支持的算法、随机数。
- Session Ticket
- Key Share:为了保险起见,依然带上新的 ECDHE 临时公钥(万一 0-RTT 失败了,可以无缝降级回 1-RTT)。
- Early Data
(加密的):这是重点!用刚才算的早期密钥加密的 HTTP 请求(如
GET /index.html)。
服务器动作:
- 收到大礼包:服务器收到这一大堆数据。
- 验证凭证:服务器解密 Session Ticket,确认它有效,并从中提取出 PSK。
- 尝试解密:服务器用提取出的 PSK,算出同样的早期密钥,尝试解密后面的 Early Data。
- 并行处理(极速的核心):
- 路径 A(应用层):如果解密成功,服务器立即把解出来的 HTTP 请求(GET /)交给后端应用(比如 Nginx/Tomcat)去处理。此时此刻,握手甚至还没完成,但服务器已经开始干活了!
- 路径 B(握手层):与此同时,服务器利用客户端发来的 Key Share,完成标准的 ECDHE 密钥协商,生成新的、具有前向安全的正式会话密钥。
- 回复:服务器发送
ServerHello+Finished(完成握手),紧接着发送用新密钥加密的 HTTP 响应(网页内容)。
效果对比:
- 1-RTT:客户端发送 -> 服务器处理握手 -> 服务器回复握手完成 -> 客户端发送 HTTP 请求 -> 服务器处理并回复网页。
- 0-RTT:客户端发送(含 HTTP 请求) -> 服务器处理握手并同时处理 HTTP 请求 -> 服务器回复握手完成和网页内容。
- 用户感受到网页加载的时间,整整少了一个 RTT。在跨国网络或移动网络下,这可能是几百毫秒的提升。

然而!天下没有免费的午餐。0-RTT 带来了极致的速度,也引入了一个巨大的安全风险。
在标准的 ECDHE 握手中,每次的密钥都是基于全新的 Client Random 和 Server Random 实时计算出来的。黑客录制了昨天的握手数据包,今天重新发给服务器,服务器一看随机数是旧的,或者因为没有对应的临时私钥无法算出正确的密钥,直接就拒绝了。
然而 0-RTT 用于加密 Early Data 的密钥,是基于以前的 PSK 衍生出来的,所以是存在重放危险的!
由于这个致命缺陷,TLS 1.3 标准明确建议:千万不要用 0-RTT 发送非幂等(Non-Idempotent)请求!
- 安全场景:
GET请求(如获取图片、网页)。重放多少次结果都一样,没副作用。 - 危险场景:
POST、PUT、DELETE等改变服务器状态的请求。
HTTP3
HTTP/3 不再使用 TCP,而是基于 Google 开发的 QUIC(Quick UDP Internet Connections),底层使用 UDP。
1. 核心机制
- 机制 1:独立的流控制(解决 TCP 队头阻塞)
- QUIC 在用户态(User Space)实现了类似 TCP 的可靠传输。
- 但它知道"流"的概念。如果 Stream 1 丢包,QUIC 只会阻塞 Stream 1,Stream 2 的数据包可以直接交给浏览器渲染。
- 机制 2:连接迁移 (Connection Migration)
- TCP:使用四元组(源 IP, 源端口, 目的 IP, 目的端口)标识连接。手机从 Wi-Fi 切到 4G,源 IP 变了,连接断开,必须重连。
- QUIC:使用 Connection ID (CID) 标识连接。只要 CID 不变,IP 变了也能继续传输,实现无缝网络切换。
- 机制 3:内置 TLS 1.3
- HTTP/3 没有把 TLS 当作上层协议,而是直接将 TLS 1.3 的握手过程嵌入到 QUIC 的建立过程中。
- 传输层握手 + 加密层握手合并,实现真正的 1-RTT 建连(TCP+TLS 需要分别握手)。
- 机制 4:用户态拥塞控制
- TCP 的拥塞控制(CUBIC, BBR)在操作系统内核里,升级困难(需要更新 OS)。
- QUIC 在应用层实现,浏览器更新一下就能换用最新的拥塞控制算法,迭代极快。
- QPACK:
- HTTP/2 的 HPACK 依赖于数据的顺序到达(更新动态表)。
- 由于 QUIC 允许乱序,HPACK 失效。HTTP/3 引入了 QPACK,专门适配乱序环境下的头部压缩。
2. 握手机制
接下来我们重点讨论 QUIC(HTTP3)的握手机制。QUIC 的握手是其最核心的创新之一,它解决了 TCP + TLS 分层架构带来的根本性低效问题。
在传统的 HTTPS (HTTP/2) 中,网络栈是分层的:
- TCP 层:先花 1-RTT(三次握手)建立可靠连接。此时数据是明文的。
- TLS 层:再花 1-RTT(TLS 1.3)在 TCP 之上协商密钥。
总共需要 2-RTT 才能开始发 HTTP 数据。
QUIC 的革命性设计:融合层级
QUIC 基于 UDP,UDP 没有连接的概念。QUIC协议栈直接包含了 TLS 1.3 模块。 QUIC的握手目标是:在建立逻辑连接的同时,完成加密密钥的协商。它把这两个步骤合并成了真正的1-RTT。
在深入 QUIC 的握手机制之前,我们需要先了解几个 QUIC 特有的概念:
Connection ID (CID, 连接 ID):
因为 UDP 没有连接,IP 地址和端口又可能会变(比如手机切网络),QUIC 使用 CID 来标识一个连接。
每个数据包头都会带上目标 CID (DCID) 和源 CID (SCID)。这使得连接可以迁移。
报文类型 (Packet Types):
QUIC 在握手阶段使用特殊的长首部(Long Header)报文。
Initial 包:用于承载 TLS 的
ClientHello和ServerHello。Handshake 包:用于承载加密后的 TLS 握手消息(如证书、Finished)。
0-RTT 包:用于承载 0-RTT 的早期应用数据。
Short Header 包:握手完成后,传输普通应用数据使用。
反放大攻击 (Anti-Amplification):
- 由于 UDP 容易被伪造 IP 进行 DDoS 攻击(发送小包骗取大包回复),QUIC 规定在验证客户端 IP 地址之前,服务器回复的数据量不能超过客户端发送数据量的 3 倍。
2.1 1-RTT
这是最基础的场景,目标是达到 TLS 1.3 的 1-RTT 效果,但不需要 TCP 握手。

步骤 1:客户端发送 Initial 包
- 外层 (QUIC):客户端生成一个随机的各种 CID。封装一个
Initial类型的 QUIC 包。 - 内层 (TLS 1.3):包含标准的 TLS
ClientHello消息。- 带着 Key Share(猜测的临时公钥,如 X25519 公钥 A)。
- 带着 TLS 版本、加密套件列表等。
- 注意:这个包本身也需要加密,但因为还没协商密钥,所以使用一个协议标准里写死的"初始密钥"进行模糊化(Obfuscation),主要为了防干扰,不防黑客。
步骤 2:服务器回复 Initial + Handshake 包
服务器收到包,提取出
ClientHello,交给内置的 TLS 模块处理。TLS 模块生成自己的临时公钥 B,算出握手密钥(Handshake Keys)。
服务器需要连续发送两个逻辑上的包(可能会合并在一个 UDP 数据报里):
- QUIC Initial 包:包裹着 TLS
ServerHello(包含服务器的临时公钥 B)。这个包用初始密钥模糊化。 - QUIC Handshake 包:包裹着 TLS 的后续消息——证书 (Certificate)、签名 (CertificateVerify)、结束消息 (Finished)。
- 关键点:这个 Handshake 包是使用刚才算出来的握手密钥加密的。这是第一批真正加密的数据。
- QUIC Initial 包:包裹着 TLS
步骤 3:客户端完成握手并发送数据
- 客户端收到
ServerHello,算出握手密钥。 - 用握手密钥解密后续的
Handshake包,验证证书和签名。 - 验证成功后,客户端算出最终的传输层会话密钥(1-RTT Keys)。
- 发送两个逻辑包:
- QUIC Handshake 包:包裹着客户端的 TLS
Finished消息(用握手密钥加密)。 - QUIC Short Header 包:包裹着真正的 HTTP/3
请求数据(如
GET /),用最终会话密钥加密。
- QUIC Handshake 包:包裹着客户端的 TLS
- 握手结束。
2.2 0-RTT
如果你理解了 TLS 1.3 的 0-RTT 原理(利用之前的 PSK/Ticket),QUIC 只是把它封装到了特定的包类型中。
所以它跟 TCP+TLS1.3 的 0-RTT 一样会存在重放危险。

步骤 1:客户端发送 Initial + 0-RTT 包(大礼包)
- 客户端找到了上次连接存下来的 Session Ticket (PSK)。
- 客户端使用 PSK 生成"早期数据密钥"。
- 客户端一股脑发送出去:
- QUIC Initial 包:包含 TLS
ClientHello,里面带着 PSK Ticket,同时也带着新的 Key Share (以防 0-RTT 失败回退到 1-RTT)。 - QUIC 0-RTT 包:使用早期密钥加密的 HTTP 请求数据。
- QUIC Initial 包:包含 TLS
步骤 2:服务器并行处理
- 服务器收到这些包。TLS 模块验证 PSK 成功。
- 关键动作(并行):
- 服务器使用 PSK 算出早期密钥,立即解密 0-RTT 包里的 HTTP 请求,交给后端应用处理。
- 同时,服务器利用
ClientHello里的新 Key Share,完成标准的 1-RTT 握手流程,生成新的、前向安全的会话密钥。
步骤 3:服务器回复
- 服务器发送
ServerHello和加密的握手消息(Finished),完成正式握手。 - 紧接着发送用新密钥加密的 HTTP 响应数据。
特殊情况:地址验证(Retry 机制)
为了防止 UDP 反放大攻击,当服务器是一个热门服务,它可能会要求客户端先证明"你确实拥有这个 IP 地址,不是伪造的"。
这会引入一个额外的 RTT:
- Client -> Server: 发送
Initial包 (ClientHello)。 - Server -> Client: 服务器感觉有风险,不进行复杂的 TLS 运算,而是回复一个很小的 QUIC Retry 包,里面包含一个加密的 Token。
- Client -> Server: 客户端收到 Retry,必须重新发送
Initial包,但这次要带上刚才收到的 Token。 - Server -> Client: 服务器验证 Token 有效,确认客户端 IP 没问题,才开始正常的 1-RTT 握手流程(回复 Certificate 等)。
虽然多了一步,但这是一个轻量级的 UDP 交互,比完整的 TCP 握手还是要快。
总结
回顾从 HTTP/1.0 到 HTTP/3 的二十多年演进历程,我们可以清晰地看到,推动 Web 协议不断升级的核心动力,始终是为了提供更快、更稳、更安全的网络体验。这是一部与网络延迟、带宽限制和计算成本进行持续抗争的历史。
- HTTP/1.0 -> HTTP/1.1:为了解决 TCP 短连接带来的巨大开销,HTTP/1.1 引入了持久连接(Keep-Alive),让多个请求可以复用同一个 TCP 连接,显著减少了握手次数,是 Web 性能优化的第一步。
- HTTP/1.1 -> HTTP/2:为了解决 HTTP/1.1 在持久连接上仍然存在的应用层队头阻塞问题,HTTP/2 引入了二进制分帧和多路复用(Multiplexing)。它允许在同一个 TCP 连接上并发处理多个请求和响应,极大地提高了带宽利用率和并发能力。
- TLS 的演进 (1.2 -> 1.3):在安全层面,TLS 协议也在不断进化。TLS 1.3 通过废除不安全的加密算法、强制使用具备前向安全的 ECDHE 密钥交换,并将握手流程极致优化至 1-RTT 甚至 0-RTT,实现了速度与安全的双重飞跃。
- HTTP/2 -> HTTP/3:为了彻底解决受制于 TCP 协议特性的传输层队头阻塞以及网络切换时的连接中断问题,HTTP/3 做出了颠覆性的改变。它基于 UDP 构建了全新的 QUIC 协议,将 TLS 1.3 的安全握手深度融合,实现了真正的 1-RTT 建连、基于 Connection ID 的无缝连接迁移,以及更高效的独立流控制。
从最初的简单文本传输,到如今基于 UDP 的高性能、高安全复合协议,HTTP 的演进之路从未停歇。未来的网络协议将如何发展?也许会更加智能化、更加适应边缘计算和物联网的需求,但其核心目标——连接你我,更快更安全——将永远不变。