说说 HTTP/1.0、HTTP/1.1、HTTP/2.0 的区别
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 HTTP 协议演进历史的了解
- 理解各版本 HTTP 的核心特性
- 了解 HTTP/2 的多路复用、头部压缩等关键技术
- 能够分析各版本优缺点及适用场景
- 了解 HTTP/3 和 QUIC 协议
核心概念(基础知识点)
HTTP 版本演进时间线
1991 HTTP/0.9 只有 GET 方法,纯文本传输
1996 HTTP/1.0 引入 POST/HEAD,增加头部,支持多媒体
1997 HTTP/1.1 持久连接、管道化、分块传输
2015 HTTP/2 二进制分帧、多路复用、头部压缩
2022 HTTP/3 基于 QUIC,UDP 传输
各版本核心特性对比
| 特性 | HTTP/1.0 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|---|
| 连接方式 | 短连接 | 长连接(默认) | 多路复用 | 基于 QUIC |
| 并发请求 | 串行 | 管道化(有限) | 多路复用 | 多路复用 |
| 传输格式 | 文本 | 文本 | 二进制 | 二进制 |
| 头部压缩 | 无 | 无 | HPACK | QPACK |
| 服务器推送 | 无 | 无 | 支持 | 支持 |
| 传输层 | TCP | TCP | TCP | UDP |
详细解答(代码示例)
HTTP/1.0 短连接示例
// 请求 1:建立 TCP 连接 -> 发送请求 -> 接收响应 -> 关闭连接
GET /index.html HTTP/1.0
Host: example.com
Connection: close
HTTP/1.0 200 OK
Content-Type: text/html
Connection: close
<html>...</html>
// TCP 连接关闭
// 请求 2:重新建立 TCP 连接
GET /style.css HTTP/1.0
Host: example.com
Connection: close
// ... 再次关闭连接
问题: 每个资源都需要新建 TCP 连接,三次握手开销大。
HTTP/1.1 长连接与管道化
// 长连接(Keep-Alive)
GET /index.html HTTP/1.1
Host: example.com
Connection: keep-alive
HTTP/1.1 200 OK
Content-Type: text/html
Connection: keep-alive
// 同一连接复用
GET /style.css HTTP/1.1
Host: example.com
Connection: keep-alive
// 连接保持打开状态
// 浏览器并发连接限制
// Chrome 默认对同一域名最多 6 个并发连接
// 域名分片(Domain Sharding)优化
// 将资源分散到多个子域名
const assets = {
images: 'https://img1.example.com/photo.jpg',
css: 'https://static.example.com/style.css',
js: 'https://cdn.example.com/app.js'
};
// 这样可以绕过 6 连接限制,但会增加 DNS 查询和 TCP 连接开销
HTTP/2 多路复用
HTTP/1.1 的队头阻塞问题:
┌──────────────────────────────────────┐
│ 请求1 │ 请求2 │ 请求3 │ 请求4 │ 请求5 │
└──────────────────────────────────────┘
如果请求1响应慢,后面的请求都要等待
HTTP/2 多路复用:
┌──────────────────────────────────────┐
│ 流1: 请求1 ─────────────────────────>│
│ 流3: 请求2 ────────────> │
│ 流5: 请求3 ────────────────────────> │
│ 流7: 请求4 ──> │
│ 流9: 请求5 ─────────────────────> │
└──────────────────────────────────────┘
多个请求在同一个 TCP 连接上交错传输
// 使用 Node.js http2 模块
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
});
server.on('stream', (stream, headers) => {
const path = headers[':path'];
// 服务器推送示例
if (path === '/index.html') {
stream.respondWithFile('index.html');
// 主动推送 CSS
stream.pushStream({ ':path': '/style.css' }, (err, pushStream) => {
if (err) throw err;
pushStream.respondWithFile('style.css');
});
}
});
server.listen(8443);
HTTP/2 二进制分帧
HTTP/1.1 文本格式:
GET / HTTP/1.1\r\n
Host: example.com\r\n
User-Agent: Mozilla/5.0\r\n\r\n
HTTP/2 二进制帧格式:
┌──────────────────────────────────────────────────────────┐
│ Length (24) │ Type (8) │ Flags (8) │ R │ Stream ID (31) │
├──────────────────────────────────────────────────────────┤
│ Payload (*) │
└──────────────────────────────────────────────────────────┘
帧类型:
- HEADERS (0x1): 头部帧
- DATA (0x0): 数据帧
- SETTINGS (0x4): 设置帧
- PRIORITY (0x2): 优先级帧
- RST_STREAM (0x3): 流重置
- PING (0x6): 心跳检测
- GOAWAY (0x7): 连接关闭
头部压缩(HPACK)
// HTTP/1.1 重复头部问题
// 每个请求都发送完整的头部信息
GET /api/users HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: application/json
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; user=john
GET /api/posts HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: application/json
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; user=john
// 大部分头部重复!
// HTTP/2 HPACK 压缩
// 使用静态表、动态表和哈夫曼编码
// 重复头部只需发送索引号
深入理解(原理剖析)
HTTP/2 的队头阻塞问题
HTTP/2 解决了应用层队头阻塞,但仍有传输层队头阻塞:
应用层(已解决):
┌─────────────────────────────────────────┐
│ 流1 │ 流3 │ 流5 │ 流7 │ 流9 │ ← 多路复用
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ TCP 连接(单个字节流) │
└─────────────────────────────────────────┘
传输层(仍存在):
如果 TCP 连接中某个数据包丢失:
┌─────────────────────────────────────────┐
│ 包1 │ 包2 │ 包3 │ ❌丢失 │ 包5 │ 包6 │
└─────────────────────────────────────────┘
↓
所有流都要等待丢包重传(TCP 队头阻塞)
HTTP/3 QUIC 解决方案:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 流1 QUIC│ │ 流3 QUIC│ │ 流5 QUIC│
│ 独立传输 │ │ 独立传输 │ │ 独立传输 │
└─────────┘ └─────────┘ └─────────┘
某个流丢包不影响其他流
HTTP/2 优先级与流量控制
流优先级:
┌─────────────────────────────────────────┐
│ 根流 (0) │
├─────────────────────────────────────────┤
│ 流 A (权重 12) │ 流 B (权重 4) │
├─────────────────────────────────────────┤
│ 流 C │ 流 D │ 流 E │ 流 F │
│ (8) │ (4) │ │ │
└─────────────────────────────────────────┘
依赖关系:
- 流 E 依赖流 A
- 流 A 和流 B 同级
- 资源分配比例:A:C:D:E = 12:8:4:0(E 依赖 A)
- B:F = 4:0
流量控制:
- 基于窗口的流量控制
- 每个流独立控制
- 连接级流量控制
HTTP/3 与 QUIC
QUIC 协议栈:
┌─────────────────────────────────────────┐
│ HTTP/3 │
├─────────────────────────────────────────┤
│ QUIC (Quick UDP Internet Connections) │
│ - 加密握手 + 传输握手合并(0-RTT/1-RTT) │
│ - 内置 TLS 1.3 │
│ - 连接迁移(Connection Migration) │
│ - 无队头阻塞的多路复用 │
├─────────────────────────────────────────┤
│ UDP │
└─────────────────────────────────────────┘
QUIC 优势:
1. 快速握手:通常 0-RTT 或 1-RTT
2. 连接迁移:网络切换(WiFi <-> 4G)保持连接
3. 改进的拥塞控制:可插拔的拥塞控制算法
4. 前向纠错:减少重传延迟
最佳实践
1. HTTP/2 服务器推送策略
// Nginx HTTP/2 服务器推送配置
server {
listen 443 ssl http2;
location = /index.html {
# 推送关键 CSS 和 JS
http2_push /css/critical.css;
http2_push /js/app.js;
}
}
// 注意:不要滥用推送
// - 只推送关键资源
// - 考虑缓存(如果客户端已缓存,推送浪费带宽)
// - 使用 Cookie 或 Service Worker 检测缓存状态
2. 资源优化策略
<!-- HTTP/1.1 优化:合并资源 -->
<!-- 不推荐:增加缓存粒度问题 -->
<link rel="stylesheet" href="all-in-one.css">
<script src="bundle.js"></script>
<!-- HTTP/2 优化:细粒度资源 -->
<!-- 推荐:更好的缓存控制,按需加载 -->
<link rel="stylesheet" href="critical.css">
<script src="core.js" type="module"></script>
<script src="feature-a.js" type="module"></script>
<!-- 使用模块预加载 -->
<link rel="modulepreload" href="feature-a.js">
3. 性能监控
// 使用 Navigation Timing API 监控性能
window.addEventListener('load', () => {
const timing = performance.timing;
// DNS 查询时间
const dnsTime = timing.domainLookupEnd - timing.domainLookupStart;
// TCP 连接时间
const tcpTime = timing.connectEnd - timing.connectStart;
// 首字节时间(TTFB)
const ttfb = timing.responseStart - timing.requestStart;
// DOM 解析时间
const domParse = timing.domComplete - timing.domLoading;
console.log('Protocol:', performance.getEntriesByType('navigation')[0].nextHopProtocol);
// 输出: h2 (HTTP/2) 或 h3 (HTTP/3) 或 http/1.1
});
4. 协议协商
# Nginx 协议协商配置
server {
listen 443 ssl http2; # 支持 HTTP/2
# ALPN 协商
ssl_alpn h2 http/1.1;
# 如果客户端不支持 HTTP/2,自动回退到 HTTP/1.1
}
面试要点
-
HTTP/1.1 vs HTTP/2 性能对比
- HTTP/2 多路复用解决了浏览器 6 连接限制
- 头部压缩减少带宽消耗(通常减少 30-50%)
- 服务器推送可以减少 RTT
- 但实际性能提升受多种因素影响
-
HTTP/2 的局限性
- TCP 队头阻塞仍然存在
- 握手延迟(TCP + TLS)
- 连接迁移困难(IP 变化需要重连)
-
HTTP/3 的核心改进
- 基于 UDP 的 QUIC 协议
- 0-RTT 连接建立
- 连接迁移支持
- 独立的流控制,无队头阻塞
-
实际部署考虑
- TLS 是 HTTP/2 的强制要求
- 需要确保中间代理支持
- 服务器推送需要谨慎使用
- HTTP/3 支持仍在普及中
-
面试高频问题
- 为什么 HTTP/2 使用二进制而非文本?(解析效率、安全性)
- HTTP/2 的优先级机制如何工作?
- 什么是 HTTP/2 的流(Stream)?
- QUIC 相比 TCP 的优势?
- 如何检测网站使用的 HTTP 版本?(Chrome DevTools Network 面板)