本文是《计算机网络学习笔记》系列的第六篇。有了 TCP 保证可靠传输,有了 TLS 保证加密安全,应用层终于可以专注于"发什么"——这就是 HTTP 的职责。从 1991 年诞生至今,HTTP 经历了三次重大进化,每一次都是对上一代痛点的定点突破。

一、三个版本,三种设计哲学

HTTP/1.1  ──────►  HTTP/2  ──────►  HTTP/3
 纯文本              二进制帧          QUIC (UDP)
 队头阻塞            多路复用          彻底解决阻塞
 Header 冗余         头部压缩          0-RTT 握手

目前三个版本同时在用。在 Chrome 的开发者工具(DevTools → Network → Protocol 列)里,可以直接看到每个请求具体走的哪个版本。


二、HTTP/1.1:纯文本时代

HTTP/1.1(1997 年)是使用时间最长的版本,核心特征是纯 ASCII 文本,人类可以直接读懂。

请求报文格式

GET /somedir/page.html HTTP/1.1\r\n        ← 请求行:方法 + URL + 版本
Host: www.example.com\r\n                  ← Header(每行 \r\n 结尾)
Connection: keep-alive\r\n
User-Agent: Mozilla/5.0\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
\r\n                                        ← 空行,标志 Header 结束
(可选的 Body,GET 请求通常没有)

请求行三个字段:

  • 方法(Method)GET(获取资源)、POST(提交数据)、PUT(创建/替换)、DELETE(删除)、HEAD(只要头部)等;
  • URL:资源路径,可以带查询参数(?key=value);
  • 版本HTTP/1.1

响应报文格式

HTTP/1.1 200 OK\r\n                        ← 状态行:版本 + 状态码 + 原因短语
Date: Tue, 18 Aug 2015 15:44:04 GMT\r\n   ← Header
Server: nginx/1.24.0\r\n
Content-Length: 6821\r\n
Content-Type: text/html; charset=utf-8\r\n
\r\n                                        ← 空行,Header 结束
<!DOCTYPE html>...                          ← Body(实际的 HTML 内容)

常见状态码速查

范围含义典型例子
1xx信息性101 Switching Protocols(WebSocket 升级)
2xx成功200 OK201 Created204 No Content
3xx重定向301 永久重定向302 临时重定向304 Not Modified(缓存命中)
4xx客户端错误400 Bad Request401 未认证403 禁止404 未找到
5xx服务端错误500 内部错误502 Bad Gateway503 服务不可用

HTTP/1.1 的改进:Keep-Alive

HTTP/1.0 每次请求都要经历"TCP 三次握手 → 发送请求 → 四次挥手"的完整流程,一个网页包含几十个资源,每个都要单独建连接,极其低效。

HTTP/1.1 引入了持久连接(Keep-Alive):一条 TCP 连接可以复用,连续发多个请求,省去了反复握手的开销。

HTTP/1.0:
  TCP 握手 → 请求1 → 响应1 → TCP 挥手
  TCP 握手 → 请求2 → 响应2 → TCP 挥手
  TCP 握手 → 请求3 → 响应3 → TCP 挥手

HTTP/1.1 Keep-Alive:
  TCP 握手
    → 请求1 → 响应1
    → 请求2 → 响应2
    → 请求3 → 响应3
  TCP 挥手(复用同一连接)

HTTP/1.1 的固有痛点

Keep-Alive 解决了"反复握手"的问题,但没有解决根本矛盾:

① 队头阻塞(Head-of-Line Blocking)

HTTP/1.1 的请求必须串行:必须等上一个响应回来,才能发下一个请求。如果某张图片很大、响应很慢,后面所有请求都得排队等待。(HTTP/1.1 虽然支持"管道化 Pipelining",但实现复杂、各浏览器支持参差不齐,几乎没有被实际采用。)

请求1(大图)————————————————→ 响应1(很慢……)
                                              → 请求2(等着)
                                              → 请求3(等着)

② Header 冗余

每次请求都要把 User-AgentCookieAccept-Language 等完整地发一遍,哪怕这些内容从来不变。一个 Cookie 动辄几百字节,几十个请求就是几十 KB 的纯冗余数据。

③ 明文传输

HTTP/1.1 是明文的,内容对任何中间节点都是透明的,没有加密(HTTPS = HTTP/1.1 + TLS,是后来叠加上去的)。


三、HTTP/2:二进制帧与多路复用

HTTP/2(2015 年,RFC 7540)是对 HTTP/1.1 痛点的系统性解决,保留了 HTTP 的语义(Method、Header、Body),彻底重构了底层传输格式。

核心变化:一切皆帧

HTTP/2 的最小通信单位是帧(Frame),所有 Header 和 Body 都被拆成帧在网络上传输。帧是二进制格式,不再是人类可读的文本。

HTTP/2 帧的标准格式(共 9 字节固定头部):

 +-----------------------------------------------+
 |                 Length (24)                   |  3 字节:Payload 的长度
 +---------------+---------------+---------------+
 |   Type (8)    |   Flags (8)   |               |  各 1 字节
 +-+-------------+---------------+---------------+
 |R|           Stream Identifier (31)            |  4 字节(R 为保留位)
 +-+---------------------------------------------+
 |                Frame Payload (...)             |  可变长度
 +-----------------------------------------------+

字段详解:

字段大小说明
Length3 字节Payload 的字节长度,不含 9 字节头部本身
Type1 字节帧类型,决定 Payload 的含义(见下表)
Flags1 字节特定于 Type 的标志位
R1 位保留位,必须为 0
Stream ID31 位流标识符,标识该帧属于哪个"请求/响应对"
Payload可变实际内容,格式由 Type 决定

常见帧类型:

Type 值名称作用
0x00DATA传输 HTTP 包体(Body)
0x01HEADERS传输 HTTP 头部(Header),使用 HPACK 压缩
0x03RST_STREAM立即终止某个流(如用户取消了加载)
0x04SETTINGS协商配置(如最大并发流数量、初始窗口大小)
0x08WINDOW_UPDATE流量控制(类似 TCP 的窗口更新)
0x09CONTINUATIONHEADERS 帧的延续(Header 太大时分多帧)

常用 Flags:

  • END_STREAM (0x1):这是当前流的最后一帧,发送方不会再发数据了(相当于"话说完了");
  • END_HEADERS (0x4):这是 Header 块的最后一帧。

多路复用:彻底解决队头阻塞

HTTP/2 引入了流(Stream)的概念:一条 TCP 连接上可以同时存在多个逻辑流,每个流承载一对独立的请求/响应,流之间互不干扰。

Stream ID 是区分流的关键:客户端发起的流使用奇数 ID(1、3、5……),服务端发起的使用偶数 ID。

HTTP/2(同一 TCP 连接上并行多流):

TCP 连接
  ├── Stream 1:请求首页 HTML   → [HEADERS] [DATA] ...
  ├── Stream 3:请求 main.css   → [HEADERS] [DATA] ...
  ├── Stream 5:请求 logo.png   → [HEADERS] [DATA] ...
  └── Stream 7:请求 app.js     → [HEADERS] [DATA] ...

(四个请求完全并行,谁先响应谁先发回来)

以前等一张大图导致所有请求阻塞的问题,在 HTTP/2 里消失了——大图走 Stream 5,其他请求走其他 Stream,互不影响。

头部压缩(HPACK)

HTTP/2 引入了 HPACK 算法来压缩 Header:

  1. 静态表:预定义了 61 个最常见的 Header 字段(如 :method: GET:status: 200),用一个 1~2 字节的索引代替整个字符串;
  2. 动态表:在连接期间,双方维护一张共享的"已传输 Header"表,后续请求若有相同的 Header,只发送其在表中的索引,不重复发原始字符串;
  3. Huffman 编码:对表中无法命中的新 Header 进行 Huffman 压缩。

实际效果:Header 体积通常可以压缩到原来的 85%~95%,对 Cookie 很大的场景(如登录后的请求)效果尤为显著。

如何抓 HTTP/2 的明文包?

HTTP/2 几乎总是跑在 TLS 之上(即 HTTPS),直接抓包只能看到密文。可以结合第五篇《TLS 加密握手》介绍的 SSLKEYLOGFILE + Wireshark 方案,在 Wireshark 里用 http2 过滤器,就能看到解密后的 HTTP/2 帧。


四、HTTP/3:彻底抛弃 TCP

HTTP/2 解决了应用层的队头阻塞,但底层 TCP 还有一个无法消除的阻塞:TCP 层的队头阻塞

当 HTTP/2 把多个流复用在同一条 TCP 连接上时,如果某个 TCP 报文丢失,TCP 必须等到该报文重传成功,才能继续向上层交付数据——哪怕丢失的数据只属于其中一个流,其余所有流也得跟着等。

根本原因:HTTP/2 解决了"应用层的队头阻塞",但 TCP 是一条有序字节流,一旦中间有洞,后面的所有数据都被堵在 TCP 缓冲区里,无法跨越这个洞送给应用层。

HTTP/3 的解法:彻底换掉 TCP,改用 QUIC

QUIC:基于 UDP 的可靠传输

QUIC(Quick UDP Internet Connections)是 Google 设计、后来由 IETF 标准化的传输层协议(RFC 9000),运行在 UDP 之上,自己实现了可靠传输、流量控制、拥塞控制等功能:

特性TCP + TLSQUIC
传输层TCPUDP(可靠性由 QUIC 自己实现)
TLS独立握手(1~2 RTT)内置于 QUIC 握手(0~1 RTT)
队头阻塞有(TCP 层)无(流之间完全独立)
连接迁移不支持(IP/端口变了连接断掉)支持(基于 Connection ID,换网络不断连)
协议头加密明文大部分头部加密,防中间人干扰

0-RTT 建连:首次连接需要 1-RTT,之后重连可以在第一个数据包里就携带应用层数据,实现 0-RTT,极大减少延迟。

连接迁移(Connection Migration):手机从 WiFi 切到 4G,IP 地址变了,TCP 连接必然断开需要重建。QUIC 使用 Connection ID 标识连接,而非 IP+端口,网络切换后连接不中断,对移动场景非常友好。

HTTP/3 在国内的现实

HTTP/3 = HTTP over QUIC。由于 QUIC 运行在 UDP 之上,而国内部分运营商对 UDP 流量限速或封锁(UDP 难以有效识别和计费),所以 HTTP/3 在国内的普及度远低于国际。

Google、YouTube、Meta 等国外大厂已全面支持 HTTP/3,但在国内网络环境下,大多数时候会回退到 HTTP/2。


五、三个版本横向对比

HTTP/1.1HTTP/2HTTP/3
数据格式ASCII 文本二进制帧二进制帧(QUIC)
连接复用Keep-Alive(串行)多路复用(并行流)多路复用(并行流)
队头阻塞应用层 + TCP 层仅 TCP 层
头部压缩HPACKQPACK
加密可选(+TLS)实践上必须(+TLS)内置(QUIC 内含 TLS 1.3)
底层传输TCPTCPUDP(QUIC)
普及程度广泛广泛国际为主

六、调试工具

Chrome DevTools

在 Network 面板的列表中右键列头,勾选 Protocol,即可看到每个请求实际走的是 http/1.1h2(HTTP/2)还是 h3(HTTP/3)。

Fiddler(Windows 首选)

Fiddler 不只是"看",还能:

  • 改包重放:截获一个请求,修改参数(如 token、参数值),重新发给服务器,是接口调试的利器;
  • 设置断点:在请求发出前或响应返回前暂停,手动修改内容;
  • 脚本自动化:编写 FiddlerScript 自动修改匹配规则下的所有请求/响应。

httpbin.org

http://httpbin.org 是一个专门用于测试 HTTP 的回显服务,支持 HTTP/1.1(可以用来练习抓包):

# 用 curl 测试一个 GET 请求
curl -v http://httpbin.org/get

# 测试 POST
curl -X POST http://httpbin.org/post -d '{"key":"value"}' -H 'Content-Type: application/json'

# 测试指定状态码
curl -v http://httpbin.org/status/404

响应会把你发的请求内容原样返回,非常适合初学时验证"我发出去的包长什么样"。


总结

HTTP 的三次进化,本质上是一次次对网络延迟的宣战

  • HTTP/1.1:确立了基本的请求-响应语义,Keep-Alive 减少了 TCP 握手次数,但队头阻塞和 Header 冗余是硬伤;
  • HTTP/2:用二进制帧和多路复用彻底解决了应用层队头阻塞,HPACK 压缩大幅减少 Header 开销,但 TCP 本身的队头阻塞无法消除;
  • HTTP/3:通过 QUIC 把 TCP 的最后一块短板也换掉,连接迁移和 0-RTT 让移动场景体验更佳,是方向正确但仍在普及中的未来。

本系列前五篇:
· 第一篇:《TCP 协议格式详解》
· 第二篇:《TCP 三次握手与四次挥手》
· 第三篇:《TCP 可靠数据传输》
· 第四篇:《TCP 流量控制与拥塞控制》
· 第5篇:《TLS:HTTPS 背后的加密握手》


参考资料:《计算机网络:自顶向下方法》

标签: none

添加新评论