网络协议与前端
前言
在前端开发中,网络协议是连接客户端与服务端的基石。深入理解 HTTP 缓存、跨域资源共享(CORS)、WebSocket、Server-Sent Events(SSE)以及 QUIC/HTTP3 等协议与机制,对于构建高性能、高可用的 Web 应用至关重要。本文将从前端视角系统性地梳理这些核心网络协议。
一、HTTP 缓存策略
HTTP 缓存是提升 Web 性能最有效的手段之一。合理配置缓存策略可以显著减少网络请求、降低服务器负载、加速页面加载。
1.1 强缓存(Strong Cache)
强缓存是指浏览器在缓存有效期内直接使用本地副本,不发起任何网络请求。强缓存由以下两个响应头控制:
Cache-Control
Cache-Control 是 HTTP/1.1 引入的缓存控制头,优先级高于 Expires。常用指令:
| 指令 | 说明 | 示例 |
|---|---|---|
max-age=<秒> | 资源在缓存中的最大有效时间 | max-age=3600 |
public | 允许所有代理服务器缓存 | public, max-age=86400 |
private | 仅允许浏览器缓存 | private, max-age=300 |
no-cache | 强制每次使用前向服务器验证 | no-cache |
no-store | 禁止任何缓存 | no-store |
immutable | 资源永不改变(配合长期缓存) | public, max-age=31536000, immutable |
Expires
Expires 是 HTTP/1.0 的缓存头,指定一个绝对过期时间。由于依赖客户端时间,可能存在偏差,建议以 Cache-Control 为主。
Expires: Wed, 17 Jul 2027 12:00:00 GMT1.2 协商缓存(Negotiation Cache)
当强缓存过期或命中 no-cache 时,浏览器会向服务器发送请求进行验证。若资源未变更,服务器返回 304 Not Modified,不返回实体内容。
ETag
ETag 是资源内容的哈希标识。浏览器在请求时通过 If-None-Match 头携带该值,服务端比较后决定是否返回 304。
响应: ETag: "abc123"
请求: If-None-Match: "abc123"
响应: 304 Not ModifiedLast-Modified
Last-Modified 记录资源的最后修改时间。浏览器通过 If-Modified-Since 头携带该值进行验证。
响应: Last-Modified: Wed, 17 Jul 2026 10:00:00 GMT
请求: If-Modified-Since: Wed, 17 Jul 2026 10:00:00 GMT
响应: 304 Not Modified优先级:
ETag优于Last-Modified(ETag 精度更高,能检测到秒级内的变更)。
1.3 缓存策略决策流程
浏览器缓存决策完整流程如下:
浏览器发起请求
│
├── 检查强缓存(Cache-Control / Expires)
│ ├── 命中 → 直接使用本地缓存(200 from disk/memory cache)
│ └── 未命中 → 进入协商缓存
│
├── 协商缓存
│ ├── 携带 If-None-Match / If-Modified-Since
│ ├── 服务端验证
│ │ ├── 资源未变更 → 304 Not Modified
│ │ └── 资源已变更 → 200 + 新资源
│ └── 浏览器更新缓存
│
└── 无缓存 → 直接请求服务器(200)1.4 配置示例
Nginx 配置
# 静态资源长期缓存
location /static/ {
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# HTML 文件—每次请求验证
location / {
add_header Cache-Control "no-cache";
}Apache 配置(.htaccess)
<FilesMatch "\.(jpg|jpeg|png|gif|ico)$">
Header set Cache-Control "public, max-age=31536000"
</FilesMatch>
<FilesMatch "\.(css|js)$">
Header set Cache-Control "public, max-age=86400"
</FilesMatch>服务端响应头示例(Node.js / Express)
app.get('/static/js/app.js', (req, res) => {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
res.setHeader('ETag', generateHash(content));
res.send(content);
});二、CORS(跨域资源共享)
当浏览器端请求的域名、协议或端口与当前页面不同时,会触发同源策略限制。CORS(Cross-Origin Resource Sharing)是 W3C 标准机制,通过 HTTP 头告知浏览器允许跨域访问。
2.1 简单请求
满足以下所有条件的请求被视为简单请求,浏览器直接发送请求,无需预检:
- 方法为
GET、HEAD或POST - 仅包含安全头部(
Accept、Accept-Language、Content-Language、Content-Type且值为application/x-www-form-urlencoded、multipart/form-data或text/plain) - 不使用
ReadableStream或事件监听器
2.2 预检请求(Preflight Request)
当请求不满足简单请求条件时,浏览器先发送一个 OPTIONS 请求(预检请求)询问服务端是否允许实际请求。
典型触发场景:
- 使用
PUT、DELETE、PATCH等方法 - 设置了非安全头部(如
Authorization、X-Custom-Header) Content-Type为application/json
预检请求头:
OPTIONS /api/data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header服务端响应头:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 864002.3 跨域方案对比
| 方案 | 原理 | 支持情况 | 适用场景 |
|---|---|---|---|
| CORS | HTTP 头协商 | 现代浏览器全面支持 | 通用跨域请求 |
| JSONP | <script> 标签不受同源限制 | 仅支持 GET | 老旧系统兼容 |
| 反向代理 | Nginx 等代理转发,绕过跨域 | 服务端配置 | 开发/生产环境统一管理 |
| postMessage | 窗口间消息通信 | 广泛支持 | iframe 父子窗口通信 |
| WebSocket | 协议本身无同源限制 | 广泛支持 | 实时双向通信 |
| Nginx 代理配置示例 |
Nginx 反向代理 CORS 配置
server {
listen 80;
server_name api.example.com;
location / {
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range, Authorization" always;
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend:8080;
}
}三、WebSocket
WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议,由 IETF 在 RFC 6455 中标准化。它使服务端能够主动向客户端推送数据。
3.1 握手过程
WebSocket 连接始于 HTTP 升级握手:
客户端请求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13服务端响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=握手成功后,连接从 HTTP 协议切换到 WebSocket 协议,此后双方可自由发送数据帧。
3.2 数据通信
WebSocket 以帧(Frame)为单位传输数据,帧类型包括:
- 文本帧:UTF-8 编码的文本数据
- 二进制帧:Blob 或 ArrayBuffer 数据
- Ping/Pong 帧:心跳检测
- 关闭帧:连接关闭
3.3 心跳机制
由于网络环境的不确定性(NAT 超时、防火墙断开等),WebSocket 连接可能在没有明显错误的情况下断开。心跳机制用于维持连接活性:
const ws = new WebSocket('wss://example.com/ws');
let heartbeatTimer = null;
const HEARTBEAT_INTERVAL = 30000; // 30 秒
function startHeartbeat() {
heartbeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, HEARTBEAT_INTERVAL);
}
ws.addEventListener('open', () => {
console.log('WebSocket 已连接');
startHeartbeat();
});
ws.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.type === 'pong') {
console.log('收到心跳回复');
}
});
ws.addEventListener('close', () => {
clearInterval(heartbeatTimer);
console.log('连接已关闭');
});3.4 重连策略
当 WebSocket 意外断开时,需要实施自动重连:
function connectWebSocket(url, options = {}) {
const {
maxRetries = 10,
baseDelay = 1000,
maxDelay = 30000,
} = options;
let retries = 0;
let ws = null;
function connect() {
ws = new WebSocket(url);
ws.addEventListener('open', () => {
retries = 0; // 成功连接后重置重试次数
});
ws.addEventListener('close', (event) => {
if (!event.wasClean && retries < maxRetries) {
const delay = Math.min(
baseDelay * Math.pow(2, retries),
maxDelay
);
retries++;
setTimeout(connect, delay);
}
});
}
connect();
return ws;
}3.5 前端 WebSocket API
// 创建连接
const socket = new WebSocket('wss://api.example.com/ws');
// 连接事件
socket.onopen = () => { /* 已连接 */ };
socket.onmessage = (event) => { /* 收到消息 */ };
socket.onerror = (error) => { /* 发生错误 */ };
socket.onclose = (event) => { /* 连接关闭 */ };
// 发送消息
socket.send(JSON.stringify({ type: 'message', content: 'Hello' }));
// 关闭连接
socket.close(1000, '正常关闭');四、SSE(Server-Sent Events)
SSE 是一种允许服务端通过 HTTP 连接向客户端单向推送事件的技术。与 WebSocket 不同,SSE 基于传统的 HTTP 协议,使用简单、兼容性好。
4.1 EventSource API
浏览器端通过 EventSource 接口消费 SSE:
const eventSource = new EventSource('/api/events');
// 监听命名事件
eventSource.addEventListener('notification', (event) => {
const data = JSON.parse(event.data);
showNotification(data.message);
});
// 监听未命名事件
eventSource.onmessage = (event) => {
console.log('收到消息:', event.data);
};
// 监听错误
eventSource.onerror = (error) => {
console.error('连接错误');
};4.2 服务端响应格式
服务端响应内容类型为 text/event-stream,格式如下:
text/event-stream
data: {"userId": 1001, "message": "你有新的消息"}
event: notification
data: {"type": "system", "content": "系统维护通知"}关键字段说明:
| 字段 | 说明 | 是否可选 |
|---|---|---|
data: | 消息数据 | 必填 |
event: | 事件类型名称 | 可选 |
id: | 事件 ID(用于断线重连) | 可选 |
retry: | 重连时间间隔(毫秒) | 可选 |
4.3 SSE 与 WebSocket 对比
| 特性 | SSE (EventSource) | WebSocket |
|---|---|---|
| 通信方向 | 服务端 → 客户端(单向) | 双向 |
| 协议 | HTTP | 独立协议(基于 TCP) |
| 自动重连 | 原生支持 | 需手动实现 |
| 二进制数据 | 不支持(仅文本) | 支持 |
| 并发连接限制 | 浏览器限制(通常 6 个) | 无特殊限制 |
| 实现复杂度 | 简单 | 较复杂 |
| 适用场景 | 实时通知、股票行情、日志流 | 聊天、游戏、实时协作 |
4.4 Node.js SSE 服务端示例
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
});
// 定期推送
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: new Date() })}\n\n`);
}, 1000);
req.on('close', () => {
clearInterval(interval);
});
}
}).listen(3000);五、QUIC / HTTP/3
5.1 QUIC 协议
QUIC(Quick UDP Internet Connections)是 Google 设计、IETF 标准化的传输层协议,基于 UDP 实现。它是 HTTP/3 的底层传输协议。
核心特性:
- 0-RTT 握手:相比 TCP+TLS 的 1-3 次 RTT 延迟,QUIC 在重复连接时实现 0-RTT 建立连接
- 连接迁移:使用连接 ID 而非 IP:端口标识连接,切换网络(如 Wi-Fi 到 4G)时连接不中断
- 多路复用无队头阻塞:单个连接上的多个流互不影响,一个流丢包不影响其他流
- 内置 TLS 1.3:默认加密,无明文传输
5.2 HTTP/3
HTTP/3 是基于 QUIC 的 HTTP 协议版本,它继承了 HTTP/2 的语义(请求/响应、头部压缩等),但将传输层从 TCP 替换为 QUIC。
HTTP/3 关键改进:
| 特性 | HTTP/2 (TCP) | HTTP/3 (QUIC) |
|---|---|---|
| 传输层 | TCP | QUIC (UDP) |
| 握手延迟 | 2-3 RTT | 0-1 RTT |
| 队头阻塞 | 存在(TCP 层面的队头阻塞) | 基本消除 |
| 连接迁移 | 不支持(IP 变化需重建) | 原生支持 |
| 加密 | 可选(HTTPS 时 TLS) | 强制内置 |
| 拥塞控制 | TCP 拥塞控制 | 可插拔拥塞控制 |
5.3 前端如何受益
- 更快的页面加载:0-RTT 握手和消除队头阻塞使首屏加载显著提速
- 更好的弱网体验:连接迁移特性使移动端在网络切换时保持连接
- 更可靠的实时通信:多路复用无队头阻塞,适合 WebSocket 等场景
目前主流 CDN 和云服务商均已支持 HTTP/3,浏览器端无需额外配置即可自动协商升级。
六、各方案综合对比
6.1 实时通信方案对比
| 特性 | WebSocket | SSE (EventSource) | 轮询 (Polling) | WebTransport |
|---|---|---|---|---|
| 方向 | 双向 | 服务端→客户端 | 客户端轮询 | 双向 |
| 传输层 | TCP / QUIC | HTTP | HTTP | QUIC |
| 协议 | ws:// / wss:// | HTTP 长连接 | HTTP 请求 | WebTransport |
| 延迟 | 低 | 低 | 中-高(取决于轮询间隔) | 极低 |
| 二进制 | 支持 | 不支持 | 支持 | 支持 |
| 自动重连 | 需手动实现 | 原生支持 | 天然支持 | 需手动实现 |
| 浏览器支持 | 广泛 | 广泛(IE 除外) | 全部 | 有限(Chrome 90+) |
| 最佳场景 | 聊天、游戏、协作 | 通知、行情、日志 | 兼容性要求极高的场景 | 超低延迟实时应用 |
6.2 HTTP 协议版本对比
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 传输层 | TCP | TCP | QUIC (UDP) |
| 多路复用 | 否(需多个连接) | 是(流复用,有队头阻塞) | 是(无队头阻塞) |
| 头部压缩 | 否 | HPACK | QPACK |
| 服务器推送 | 否 | 是 | 是 |
| 连接建立 | 2 RTT | 2 RTT+ | 0-1 RTT |
| 加密 | 可选 | 可选(通常 TLS) | 强制 |
| 浏览器支持 | 全部 | 主流浏览器 | Chrome 87+, Firefox 88+, Safari 14+ |
6.3 缓存策略对比
| 策略 | 验证方式 | 是否发请求 | 更新及时性 | 适用资源 |
|---|---|---|---|---|
| 强缓存 | 本地时间/有效期 | 否 | 滞后(过期后才更新) | 静态资源(图片、字体) |
| 协商缓存 | ETag / Last-Modified | 是(返回轻量 304) | 实时 | HTML、CSS、JS |
| 无缓存 | 每次都请求完整资源 | 是(200) | 实时 | 动态数据 |
| 内存缓存 | 内存 | 否 | 会话内 | 当前页面资源 |
| Service Worker | 脚本逻辑 | 可定制 | 可定制 | PWA 离线资源 |
七、总结
网络协议是前端性能优化的核心战场。缓存策略减少不必要的网络开销,CORS 保障安全跨域通信,WebSocket 和 SSE 分别服务于双向和单向推送场景,而 QUIC/HTTP/3 正在引领新一代低延迟传输革命。
在实际项目中,应根据业务场景选择合适的技术组合:
- 静态资源:强缓存 + 内容哈希
- API 接口:协商缓存或 no-cache
- 实时通知:SSE(简单场景)或 WebSocket(复杂双向场景)
- 低延迟实时应用:WebSocket 或 WebTransport
- 跨域需求:优先 CORS,必要时反向代理
八、在线演示
以下是一个基于 SSE 的实时聊天 Demo,展示了 WebSocket + SSE 的混合使用场景: