📢 前言
在前端开发中,实时数据获取是常见需求。本文从前端视角对比三种主流方案:WebSocket 、SSE (Server-Sent Events) 和 setInterval 轮询,帮你做出最佳技术选型。
📊 核心对比表格
特性 | WebSocket | SSE | setInterval 轮询 |
---|---|---|---|
通信方向 | 全双工(双向通信) | 单工(服务端→客户端) | 半双工(客户端主动拉取) |
实时性 | 毫秒级 | 亚秒级 | 依赖轮询间隔(最低秒级) |
协议类型 | 独立协议(ws:// /wss:// ) |
基于 HTTP 长连接 | 基于 HTTP 短连接 |
前端复杂度 | 高(需管理连接状态) | 中(事件监听) | 低(定时器 + fetch) |
移动端电量消耗 | 中 | 中 | 高(频繁唤醒射频模块) |
兼容性 | IE10+ | Edge12+(需 Polyfill) | 全兼容 |
🔍 技术方案详解
1. WebSocket
📌 核心特性
- 双向实时通信:客户端与服务端可同时发送消息
- 低延迟:适用于高频交互场景(如在线游戏、聊天室)
- 二进制支持:可传输 ArrayBuffer、Blob 等二进制数据
💻 前端实现
javascript
const ws = new WebSocket('wss://api.example.com');
// 连接成功
ws.onopen = () => {
console.log('WebSocket connected');
ws.send('Hello Server!');
};
// 接收消息
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
// 错误处理
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
// 关闭连接
ws.onclose = () => {
console.log('WebSocket closed');
};
⚠️ 注意事项
- 需手动实现心跳检测防止断开
- iOS 后台运行超过 30 秒可能断开连接
- 浏览器同一域名最多保持 6 个 WebSocket 连接
2. SSE (Server-Sent Events)
📌 核心特性
- 服务端推送:服务端可主动向客户端推送数据
- 自动重连:内置断线重连机制(默认 3 秒)
- 轻量级:基于 HTTP 协议,兼容现有基础设施
💻 前端实现
javascript
const sse = new EventSource('/api/sse');
// 监听自定义事件
sse.addEventListener('stock_update', (e) => {
console.log('Stock price:', JSON.parse(e.data));
});
// 通用消息监听
sse.onmessage = (e) => {
console.log('Message:', e.data);
};
// 错误处理(会自动重连)
sse.onerror = () => {
console.error('SSE connection error');
};
⚠️ 注意事项
- 不支持 IE 浏览器(需使用
eventsource
polyfill) - 只能传输文本数据(二进制需 Base64 编码)
- Chrome 同一域名最多 6 个 SSE 连接
3. setInterval 轮询
📌 核心特性
- 简单易用:无需服务端特殊支持
- 兼容性极佳:所有浏览器均支持
- 成本可控:适合低频更新场景
💻 前端实现
javascript
let pollTimer;
// 启动轮询
function startPolling() {
pollTimer = setInterval(async () => {
try {
const res = await fetch('/api/data');
const data = await res.json();
updateUI(data);
} catch (err) {
console.error('Polling error:', err);
}
}, 5000); // 5秒间隔
}
// 停止轮询
function stopPolling() {
clearInterval(pollTimer);
}
// 页面隐藏时暂停轮询
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
stopPolling();
} else {
startPolling();
}
});
⚠️ 注意事项
- 高频轮询会导致性能问题(推荐间隔 ≥30 秒)
- 移动端电量消耗显著增加
- 可能产生陈旧数据(需配合 ETag 等机制)
🚀 选型建议
决策流程图
javascript
是否需要双向通信?
├── 是 → 选择 WebSocket
└── 否 → 是否需要服务端主动推送?
├── 是 → 选择 SSE
└── 否 → 数据更新频率?
├── ≤30秒 → 优先考虑 SSE
└── >30秒 → setInterval 轮询
场景化推荐
场景 | 推荐方案 | 理由 |
---|---|---|
在线聊天/多人游戏 | WebSocket | 需要双向高频通信 |
股票行情/实时日志 | SSE | 服务端主动推送,节省带宽 |
天气预报/配置更新 | setInterval 轮询 | 低频请求,实现简单 |
🛠 优化技巧
WebSocket
🔧 心跳包与 Nginx 超时的关系
核心原则
-
心跳间隔 < Nginx 超时时间
例如:若 Nginx 配置
proxy_read_timeout 60s
,则建议心跳间隔设为 50-55秒,需满足:心跳间隔 + 网络抖动缓冲 < Nginx超时时间
-
使用二进制协议(如 Protocol Buffers)压缩数据
-
添加心跳包检测连接状态
javascript
// WebSocket 心跳机制
const HEARTBEAT_INTERVAL = 50 * 1000; // 50秒
let heartbeatTimer;
ws.onopen = () => {
// 开启心跳
heartbeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'heartbeat' }));
}
}, HEARTBEAT_INTERVAL);
// 重置心跳计数器
ws.onmessage = () => {
clearTimeout(heartbeatTimer);
heartbeatTimer = setInterval(...); // 重新计时
};
};
SSE
- 利用
lastEventId
实现断点续传
javascript
// 从 localStorage 读取最后事件ID
let lastEventId = localStorage.getItem('lastSSEId') || '0';
const sse = new EventSource(`/api/sse?lastId=${lastEventId}`);
sse.addEventListener('message', (e) => {
// 更新最后事件ID
lastEventId = e.lastEventId;
localStorage.setItem('lastSSEId', lastEventId);
// 处理数据
console.log('收到数据:', e.data);
});
sse.addEventListener('error', () => {
sse.close();
setTimeout(() => {
// 断线重连时自动携带 lastEventId
new EventSource(`/api/sse?lastId=${lastEventId}`);
}, 3000);
});
setInterval 轮询
- 使用智能退避策略
javascript
let retryCount = 0;
function smartPoll() {
fetch('/api/data')
.then(resetRetry)
.catch(() => {
const delay = Math.min(2000 * 2 ** retryCount, 30000);
setTimeout(smartPoll, delay);
retryCount++;
});
}
💡 总结
方案 | 核心优势 | 最佳场景 |
---|---|---|
WebSocket | 双向实时通信 | 在线协作、实时游戏 |
SSE | 服务端推送 + 自动重连 | 监控仪表盘、实时通知 |
setInterval 轮询 | 简单易用 + 全兼容 | 低频数据更新、兼容性要求高场景 |
最终建议:根据业务需求选择合适方案,对于复杂场景可组合使用(如 WebSocket + SSE)实现最佳效果!