mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2172 字
6 分钟
HTTP/2:多路复用
2023-12-31

在上一篇实验中,了解了 HTTP/1.1 引入的持久连接(Persistent Connection)和管道化(Pipelining)。这些改进缓解了 HTTP/1.0「每个请求新建连接」的性能问题,但 HTTP/1.1 仍然存在几个根本性的瓶颈:

队头阻塞(Head-of-Line Blocking):HTTP/1.1 的管道化允许在一个 TCP 连接上发送多个请求,但响应必须按顺序返回。如果第一个请求的处理时间很长,后续请求的响应都会被阻塞——即使它们已经处理完毕。

冗余的头部:HTTP/1.x 的头部是纯文本格式,每次请求都要携带完整的头部信息。一个典型的网页可能需要加载 80-100 个资源,每个请求的头部动辄几百字节,Cookie、User-Agent 等重复头部累计起来是巨大的浪费。

有限的优先级控制:HTTP/1.1 没有标准化的请求优先级机制。浏览器无法告诉服务器「先发送 CSS,再发送图片」,导致关键资源可能被非关键资源阻塞。

2012 年,Google 提出了 SPDY 协议(读作「speedy」),旨在解决这些问题。SPDY 的设计理念包括:多路复用、头部压缩、服务器推送。这些特性被证明非常有效,最终成为 HTTP/2 的基础。2015 年,HTTP/2 作为 RFC 7540 正式发布。

HTTP/2 的核心改进包括:

  • 二进制分帧层(Binary Framing Layer):将 HTTP 消息分解为更小的帧,在 TCP 连接上交织传输
  • 多路复用(Multiplexing):单个 TCP 连接上并行处理多个请求和响应
  • 头部压缩(HPACK):使用索引表和 Huffman 编码大幅压缩头部
  • 服务器推送(Server Push):服务器可以主动向客户端推送资源
  • 流优先级(Stream Priority):客户端可以指定请求的优先级

一、核心概念详解#

1.1 二进制分帧层:帧、流、消息#

HTTP/2 最根本的改变是引入了二进制分帧层。HTTP/1.x 是基于文本的,请求和响应以换行符分隔;HTTP/2 则将所有数据分解为更小的二进制帧。

┌─────────────────────────────────────────────────────────────┐
│ TCP Connection │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ HTTP/2 Connection │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Stream 1│ │ Stream 3│ │ Stream 5│ │ Stream 7│ │ │
│ │ │ ( Req ) │ │ ( Req ) │ │ ( Resp )│ │ ( Push )│ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ ▼ ▼ ▼ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Framed Binary Data │ │ │
│ │ │ ┌─────┐┌─────┐┌─────┐┌─────┐┌─────┐┌─────┐ │ │ │
│ │ │ │Frame││Frame││Frame││Frame││Frame││Frame│ │ │ │
│ │ │ │ 1 ││ 3 ││ 5 ││ 1 ││ 3 ││ 5 │ │ │ │
│ │ │ └─────┘└─────┘└─────┘└─────┘└─────┘└─────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

三个核心概念

  • 帧(Frame):HTTP/2 通信的最小单位。每个帧包含帧头(标识长度、类型、标志位、流 ID)和帧体。
  • 流(Stream):已建立的 TCP 连接内的双向字节流,可以承载一条或多条消息。每个流有唯一的整数 ID。
  • 消息(Message):完整的 HTTP 请求或响应,由一个或多个帧组成。
HTTP/2 帧格式(9 字节帧头 + 可变长度帧体):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length (24) | Type (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
帧类型(Type 字段):
- 0x00: DATA - 请求/响应体数据
- 0x01: HEADERS - 请求/响应头部
- 0x02: PRIORITY - 流优先级
- 0x03: RST_STREAM - 终止流
- 0x04: SETTINGS - 连接配置
- 0x05: PUSH_PROMISE - 服务器推送承诺
- 0x06: PING - 心跳检测
- 0x07: GOAWAY - 关闭连接
- 0x08: WINDOW_UPDATE - 流量控制
- 0x09: CONTINUATION - 延续头部块

1.2 多路复用:单连接并行传输#

多路复用是 HTTP/2 最直观的性能提升。在 HTTP/1.1 中,即使使用持久连接和管道化,浏览器通常也会对每个域名开启 6 个 TCP 连接来并行加载资源。HTTP/2 只需要一个 TCP 连接就能并行传输所有资源。

HTTP/1.1 管道化(响应必须按序返回):
Client Server
│ │
│──── Request 1 ─────────>│
│──── Request 2 ─────────>│
│──── Request 3 ─────────>│
│ │ (处理 Request 1...)
│<─── Response 1 ─────────│
│ │ (Request 2 的响应必须等 1 完成)
│<─── Response 2 ─────────│
│<─── Response 3 ─────────│
│ │
HTTP/2 多路复用(响应可乱序返回):
Client Server
│ │
│──── Frame (Stream 1) ──>│
│──── Frame (Stream 3) ──>│
│──── Frame (Stream 5) ──>│
│<─── Frame (Stream 3) ───│ (Stream 3 先处理完,先返回)
│<─── Frame (Stream 1) ───│
│<─── Frame (Stream 5) ───│
│──── Frame (Stream 1) ──>│ (继续发送流 1 的数据)
│<─── Frame (Stream 1) ───│
│ │

多路复用的优势:

  • 消除队头阻塞:每个流独立处理,一个流的延迟不影响其他流
  • 减少连接数:单个 TCP 连接承载所有请求,减少 TCP 握手开销
  • 更高效的 TCP 利用:单一长连接让 TCP 拥塞控制更稳定

1.3 头部压缩:HPACK 算法#

HTTP/1.x 的头部是纯文本,每次请求都完整传输。假设一个请求头部有 800 字节,100 个请求就要传输 80KB 的头部数据——其中大部分是重复的。

HPACK 是 HTTP/2 的头部压缩算法,核心思想是:

  1. 静态字典:预定义 61 个常用头部名称和值,如 :method GET:status 200content-type text/html
  2. 动态字典:连接期间维护的增量字典,存储之前传输过的头部
  3. Huffman 编码:对头部值进行 Huffman 压缩
静态字典示例(索引号 -> 名称/值):
索引 名称 值
---- ------------------- -------------------
1 :authority
2 :method GET
3 :method POST
4 :path /
5 :path /index.html
...
14 :status 200
15 :status 204
16 :status 206
...
33 date
34 etag
35 location
...
61 user-agent
压缩示例:
原始 HTTP/1.1 请求头:
:method: GET
:path: /index.html
:host: example.com
user-agent: Mozilla/5.0 ...
HPACK 压缩后(伪代码表示):
[索引 2] // :method GET(静态字典)
[索引 5] // :path /index.html(静态字典)
[索引 1, "example.com"] // :authority(静态名称 + 动态值)
[索引 61, Huffman编码值] // user-agent(静态名称 + Huffman 编码值)
压缩率可达 85-90%

1.4 服务器推送:预加载资源#

HTTP/2 允许服务器在客户端请求之前主动推送资源。当客户端请求 index.html 时,服务器可以同时推送 style.cssmain.js,因为服务器知道 HTML 通常会引用这些资源。

传统模式(客户端驱动):
Client Server
│ │
│──── GET /index.html ─────────────>│
│<─── 200 OK (HTML) ────────────────│
│ │
│ (解析 HTML,发现需要 style.css) │
│ │
│──── GET /style.css ─────────────>│
│<─── 200 OK (CSS) ─────────────────│
│ │
│ (继续解析,发现需要 main.js) │
│ │
│──── GET /main.js ────────────────>│
│<─── 200 OK (JS) ──────────────────│
HTTP/2 服务器推送:
Client Server
│ │
│──── GET /index.html (Stream 1) ──>│
│ │ (服务器知道 HTML 需要 CSS/JS)
│<─── HEADERS (Stream 1, HTML) ─────│
│<─── PUSH_PROMISE (Stream 2, CSS) ─│ (承诺推送 CSS)
│<─── PUSH_PROMISE (Stream 3, JS) ─│ (承诺推送 JS)
│<─── HEADERS (Stream 2, CSS) ─────│
│<─── DATA (Stream 2, CSS body) ───│
│<─── HEADERS (Stream 3, JS) ──────│
│<─── DATA (Stream 1, HTML body) ──│
│<─── DATA (Stream 3, JS body) ────│

注意:服务器推送需要客户端接受(现代浏览器支持 RST_STREAM 拒绝不需要的推送),且 HTTP/3 中服务器推送的支持变得可选。

1.5 流优先级#

HTTP/2 允许客户端为每个流指定优先级,确保关键资源(如 CSS、HTML)优先传输。

优先级通过两种机制表达:

  1. 依赖关系:一个流可以依赖另一个流,形成依赖树
  2. 权重:依赖同一父流的子流之间按权重分配资源
优先级依赖树示例:
Root
┌───────────┼───────────┐
│ │ │
HTML (1) CSS (3) Images (5)
│ │ ┌───┼───┐
│ │ │ │ │
JS (7) Fonts (9) img1 img2 img3
规则:
- HTML 加载完成前,CSS 和 Images 可能部分加载
- CSS 优先于 Images(因为 CSS 阻塞渲染)
- JS 可能在 HTML 之后加载

二、实验一:用 Python 观察 HTTP/2 连接建立#

由于 HTTP/2 的二进制特性,无法像 HTTP/1.x 那样用 nc 手动发送请求。需要借助 Python 的 httpx 库来观察 HTTP/2 的行为。

#!/usr/bin/env python3
# http2_client.py -- HTTP/2 client for learning
import asyncio
import httpx
async def test_http2():
# httpx 支持 HTTP/2,需要安装 httpx[http2]
async with httpx.AsyncClient(http2=True) as client:
# 发送请求到一个支持 HTTP/2 的服务器
response = await client.get("https://nghttp2.org")
print(f"HTTP Version: {response.http_version}")
print(f"Status: {response.status_code}")
print(f"Headers: {dict(response.headers)}")
print(f"Content length: {len(response.content)} bytes")
# 发送多个并发请求(多路复用)
urls = [
"https://nghttp2.org/httpbin/get",
"https://nghttp2.org/httpbin/headers",
"https://nghttp2.org/httpbin/ip",
]
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
print("\n=== Multiplexed requests ===")
for i, resp in enumerate(responses):
print(f"Request {i+1}: {resp.status_code} ({len(resp.content)} bytes)")
if __name__ == "__main__":
asyncio.run(test_http2())

运行前安装依赖:

pip install "httpx[http2]"
python http2_client.py

输出示例:

HTTP Version: HTTP/2
Status: 200
Headers: {'date': '...', 'content-type': 'text/html', ...}
Content length: 12345 bytes
=== Multiplexed requests ===
Request 1: 200 (456 bytes)
Request 2: 200 (789 bytes)
Request 3: 200 (123 bytes)

2.1 用 nghttp 工具观察帧级别交互#

nghttp 是 nghttp2 项目提供的命令行工具,可以显示 HTTP/2 的帧级别交互:

# 安装 nghttp2 工具
# Ubuntu/Debian: sudo apt install nghttp2-client
# macOS: brew install nghttp2
# 使用 -v 选项显示详细信息
nghttp -v https://nghttp2.org

输出示例(截取关键部分):

[ 0.000] Connected
[ 0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.001] send HEADERS frame <length=45, flags=0x05, stream_id=1>
; END_STREAM | END_HEADERS
(padlen=0)
:method: GET
:path: /
:scheme: https
:authority: nghttp2.org
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.43.0
[ 0.050] recv SETTINGS frame <length=24, flags=0x00, stream_id=0>
(niv=4)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
...
[ 0.100] recv HEADERS frame <length=256, flags=0x04, stream_id=1>
; END_HEADERS
:status: 200
date: ...
content-type: text/html
...
[ 0.150] recv DATA frame <length=4096, flags=0x00, stream_id=1>
[ 0.160] recv DATA frame <length=4096, flags=0x00, stream_id=1>
[ 0.170] recv DATA frame <length=825, flags=0x01, stream_id=1>
; END_STREAM

从输出可以清晰看到:

  1. 连接建立后首先发送 SETTINGS 帧(协商连接参数)
  2. 然后发送 HEADERS 帧(请求头)
  3. 服务器返回 SETTINGSHEADERSDATA
  4. END_STREAM 标志表示流结束

三、实验二:用 Wireshark 抓包分析 HTTP/2 帧#

HTTP/2 通常运行在 TLS 之上(h2),但可以用 nghttp2 的 nghttpd 服务器在明文模式下运行 HTTP/2,方便抓包分析。

步骤 1:创建测试文件和启动 HTTP/2 服务器

#!/usr/bin/env python3
# http2_server.py -- Simple HTTP/2 server for learning
# 需要安装: pip install "hypercorn[http2]" 或使用 nghttpd
# 更简单的方式:使用 nghttpd(nghttp2 工具包的一部分)
# 创建测试文件目录
import os
import subprocess
WWW_DIR = "www_http2"
os.makedirs(WWW_DIR, exist_ok=True)
# 创建测试文件
with open(os.path.join(WWW_DIR, "index.html"), "w") as f:
f.write("""<!DOCTYPE html>
<html>
<head>
<title>HTTP/2 Demo</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>HTTP/2 Demo Server</h1>
<p>This page is served over HTTP/2!</p>
<img src="image.png" alt="demo">
<script src="script.js"></script>
</body>
</html>""")
with open(os.path.join(WWW_DIR, "style.css"), "w") as f:
f.write("body { font-family: sans-serif; margin: 2rem; }\nh1 { color: #333; }")
with open(os.path.join(WWW_DIR, "script.js"), "w") as f:
f.write("console.log('HTTP/2 loaded');")
print(f"Created test files in {WWW_DIR}/")
print("\nTo start HTTP/2 server (requires nghttpd):")
print(f" nghttpd -v 8443 {WWW_DIR}/")
print("\nThen test with:")
print(" nghttp -v http://localhost:8443/index.html")

步骤 2:启动明文 HTTP/2 服务器

# 创建测试目录和文件
mkdir -p www_http2
echo '<html><body><h1>HTTP/2 Test</h1></body></html>' > www_http2/index.html
# 启动 nghttpd 服务器(明文 HTTP/2)
nghttpd -v 8443 www_http2/

步骤 3:用 Wireshark 或 tcpdump 抓包

# 方法 1:使用 tcpdump 抓包
sudo tcpdump -i lo -w http2.pcap port 8443
# 方法 2:在另一个终端发起请求
nghttp -v http://localhost:8443/index.html
# 停止 tcpdump 后用 Wireshark 打开 http2.pcap 分析

在 Wireshark 中,你可以看到:

  • 连接前言(Client Magic):客户端发送的 HTTP/2 连接初始化字符串
  • SETTINGS 帧:连接参数协商
  • HEADERS 帧:压缩后的请求/响应头
  • DATA 帧:请求/响应体
  • WINDOW_UPDATE 帧:流量控制
  • PING 帧:心跳检测

四、实验三:对比 HTTP/1.1 和 HTTP/2 的性能#

来编写一个简单的性能测试脚本,对比加载多个资源时两种协议的差异:

#!/usr/bin/env python3
# http_comparison.py -- Compare HTTP/1.1 vs HTTP/2 performance
import asyncio
import time
import httpx
# 测试目标:一个包含多个资源请求的页面
BASE_URL = "https://nghttp2.org"
RESOURCES = [
"/httpbin/get",
"/httpbin/headers",
"/httpbin/ip",
"/httpbin/user-agent",
"/httpbin/cache",
]
async def load_with_http_version(http2: bool, concurrency: int = 5):
"""使用指定 HTTP 版本加载资源"""
version = "HTTP/2" if http2 else "HTTP/1.1"
print(f"\n=== Testing with {version} ===")
async with httpx.AsyncClient(http2=http2) as client:
start = time.perf_counter()
# 并发请求所有资源
tasks = [client.get(f"{BASE_URL}{path}") for path in RESOURCES[:concurrency]]
responses = await asyncio.gather(*tasks)
end = time.perf_counter()
total_bytes = sum(len(r.content) for r in responses)
print(f" Resources: {len(responses)}")
print(f" Total bytes: {total_bytes}")
print(f" Time: {(end - start) * 1000:.2f} ms")
print(f" All successful: {all(r.status_code == 200 for r in responses)}")
return end - start
async def main():
print("=== HTTP/1.1 vs HTTP/2 Performance Comparison ===")
print(f"Target: {BASE_URL}")
print(f"Resources per test: {len(RESOURCES)}")
# 运行多次取平均
runs = 3
http1_times = []
http2_times = []
for i in range(runs):
print(f"\n--- Run {i + 1}/{runs} ---")
http1_times.append(await load_with_http_version(http2=False))
http2_times.append(await load_with_http_version(http2=True))
await asyncio.sleep(0.5) # 冷却
avg_http1 = sum(http1_times) / len(http1_times) * 1000
avg_http2 = sum(http2_times) / len(http2_times) * 1000
print("\n" + "=" * 50)
print("RESULTS SUMMARY")
print("=" * 50)
print(f"HTTP/1.1 average: {avg_http1:.2f} ms")
print(f"HTTP/2 average: {avg_http2:.2f} ms")
print(f"Speedup: {avg_http1 / avg_http2:.2f}x")
print("=" * 50)
if __name__ == "__main__":
asyncio.run(main())

运行结果示例:

=== HTTP/1.1 vs HTTP/2 Performance Comparison ===
Target: https://nghttp2.org
Resources per test: 5
--- Run 1/3 ---
=== Testing with HTTP/1.1 ===
Resources: 5
Total bytes: 2456
Time: 456.78 ms
All successful: True
=== Testing with HTTP/2 ===
Resources: 5
Total bytes: 2456
Time: 123.45 ms
All successful: True
==================================================
RESULTS SUMMARY
==================================================
HTTP/1.1 average: 445.23 ms
HTTP/2 average: 118.67 ms
Speedup: 3.75x
==================================================

性能提升的原因:

  1. 多路复用:HTTP/2 在单个 TCP 连接上并行发送所有请求,避免了 HTTP/1.1 的连接管理开销
  2. 头部压缩:减少了传输的数据量
  3. 减少 TCP 连接数:HTTP/1.1 通常需要建立多个 TCP 连接来实现并行

五、实验四:观察 HPACK 头部压缩#

HTTP/2 的 HPACK 压缩效果可以通过对比请求头大小来观察:

#!/usr/bin/env python3
# hpack_demo.py -- Demonstrate HPACK compression
import httpx
def show_headers_comparison():
"""展示 HTTP/1.1 和 HTTP/2 的头部差异"""
# 模拟一组典型的请求头
headers = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"accept-encoding": "gzip, deflate, br",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
"cache-control": "max-age=0",
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
"referer": "https://example.com/",
}
# HTTP/1.1 头部大小(文本格式)
http1_header_size = sum(len(k) + len(v) + 4 for k, v in headers.items()) # ": " + "\r\n"
print("=== HTTP/1.1 Headers (plain text) ===")
for k, v in headers.items():
print(f" {k}: {v}")
print(f"Total size: {http1_header_size} bytes\n")
# HTTP/2 HPACK 压缩后的大小(估算)
# 静态字典可以完全匹配的头部会被压缩到 1 字节
# 部分匹配的被压缩到几字节
# Huffman 编码可以额外压缩 20-30%
print("=== HTTP/2 Headers (HPACK compressed) ===")
print("After HPACK compression (estimated):")
print(" :method: GET -> [index 2] = 1 byte (static table)")
print(" :path: / -> [index 4] = 1 byte (static table)")
print(" :scheme: https -> [index 7] = 1 byte (static table)")
print(" accept: ... -> ~45 bytes (Huffman encoded)")
print(" user-agent: ... -> ~35 bytes (indexed + Huffman)")
print(" ...")
print(f"Estimated compressed size: ~80-100 bytes")
print(f"Compression ratio: ~{http1_header_size / 90:.1f}x smaller")
print("\n=== Key HPACK Features ===")
print("1. Static table: 61 pre-defined common header name/value pairs")
print("2. Dynamic table: stores headers seen during connection")
print("3. Huffman coding: compresses string values")
print("4. Differential encoding: subsequent requests reference previous headers")
if __name__ == "__main__":
show_headers_comparison()

输出:

=== HTTP/1.1 Headers (plain text) ===
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,en;q=0.8
cache-control: max-age=0
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36
referer: https://example.com/
Total size: 286 bytes
=== HTTP/2 Headers (HPACK compressed) ===
After HPACK compression (estimated):
:method: GET -> [index 2] = 1 byte (static table)
:path: / -> [index 4] = 1 byte (static table)
:scheme: https -> [index 7] = 1 byte (static table)
accept: ... -> ~45 bytes (Huffman encoded)
user-agent: ... -> ~35 bytes (indexed + Huffman)
...
Estimated compressed size: ~80-100 bytes
Compression ratio: ~3.2x smaller
=== Key HPACK Features ===
1. Static table: 61 pre-defined common header name/value pairs
2. Dynamic table: stores headers seen during connection
3. Huffman coding: compresses string values
4. Differential encoding: subsequent requests reference previous headers

六、HTTP/2 的局限与 HTTP/3 的诞生#

HTTP/2 解决了 HTTP 层面的队头阻塞,但 TCP 层面的队头阻塞依然存在。当 TCP 包丢失时,整个 TCP 连接上的数据传输都会被阻塞,直到丢失的包被重传。

HTTP/2 多路复用但仍存在 TCP 层队头阻塞:
TCP 连接
├── Stream 1: Frame 1 [OK] ──> Frame 2 [LOST] ──> Frame 3 [WAITING...]
│ │
├── Stream 3: Frame 1 [OK] ──> Frame 2 [WAITING...] ──┘
│ (TCP 必须按序交付)
└── Stream 5: Frame 1 [WAITING...]
(所有流都被阻塞)

这就是 HTTP/3 使用 QUIC(基于 UDP)的原因:在传输层也实现多路复用,彻底消除队头阻塞。

七、观察总结#

通过这次实验,你应该能够:

理解 HTTP/2 的核心改进:二进制分帧层将 HTTP 消息分解为帧,支持多路复用、头部压缩、服务器推送和流优先级。

掌握帧、流、消息的关系:帧是最小单位,流是双向字节流,消息是完整的 HTTP 请求/响应。一个消息可能分多个帧传输,多个流可以交错传输。

理解 HPACK 压缩原理:静态字典、动态字典和 Huffman 编码共同作用,将头部大小减少 85-90%。

能用工具观察 HTTP/2 帧:使用 nghttp -v 观察帧级别交互,用 Wireshark 分析连接建立和数据传输过程。

认识到 HTTP/2 的局限:解决了 HTTP 层的队头阻塞,但 TCP 层的队头阻塞仍存在,这推动了 HTTP/3 的诞生。

HTTP/2 标志着 HTTP 从「文本协议」向「二进制协议」的根本转变,是 Web 性能优化的。理解 HTTP/2 的设计,是理解现代 Web 性能优化的关键。


参考#

  • RFC 7540 — Hypertext Transfer Protocol Version 2 (HTTP/2)

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

HTTP/2:多路复用
https://blog.souloss.com/posts/web/http-2/
作者
Souloss
发布于
2023-12-31
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时