你还在使用 WebSocket 实现实时消息推送吗?
一、写作背景与核心主题
本文针对服务端主动向客户端推送数据的常见业务场景(数据大屏实时刷新、消息中心未读提醒、在线聊天等),对比分析了三类主流实现方案的优劣,重点介绍了 SSE(Server-Sent Events,服务端推送事件) 的使用场景、核心API与落地实践,帮助开发者根据业务需求选择更合适的推送方案。
二、三类实时推送方案全面对比
| 方案 | 核心原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 轮询 | 客户端定时/不间断向服务端发起HTTP请求,模拟“推送”效果 | 实现简单,兼容所有浏览器 | 1. 每次请求都要经历HTTP建连/断连流程,资源浪费严重 2. 长期占用浏览器并发名额(Chrome同域名并发限制为6) 3. 轮询间隔短则耗资源,间隔长则实时性差 | 仅作为浏览器完全不支持WebSocket和SSE的兜底方案,不推荐常规使用 |
| WebSocket | 基于ws/wss协议的全双工双向通信,客户端和服务端可随时互相发送数据 | 1. 双向通信能力强 2. 实时性极高 3. 现代浏览器兼容性良好 | 1. 是独立于HTTP的新协议,需要服务端额外适配支持 2. 协议复杂度高,相对“重量级” 3. 断线重连需要自行实现 | 需要双向交互的场景:在线聊天、多人协作编辑、实时游戏等 |
| SSE | 基于HTTP/1.1的单向长连接,仅支持服务端主动向客户端推送数据 | 1. 轻量,协议复杂度远低于WebSocket 2. 完全复用现有HTTP服务端生态,无需额外适配 3. 默认支持断线重连 4. 客户端资源消耗低 5. 支持自定义数据类型 | 1. 仅支持单向通信(服务端→客户端) 2. IE浏览器、小程序不支持 | 仅需服务端单向推送的场景:数据大屏实时数据、消息中心通知、系统公告、日志实时输出等 |
三、SSE核心知识点详解
1. 连接状态(readyState)
通过EventSource实例的readyState属性可获取当前连接状态:
0(对应常量EventSource.CONNECTING):连接未建立,或连接已断线正在重试1(对应常量EventSource.OPEN):连接已建立,可正常接收数据2(对应常量EventSource.CLOSED):连接已断开,且不会重连
2. 核心事件监听
| 事件名 | 触发时机 | 典型用途 |
|---|---|---|
open | 连接成功建立时触发 | 初始化页面状态、打印连接成功日志 |
message | 收到服务端推送的数据时触发 | 解析数据并更新页面UI |
error | 发生通信错误、连接中断时触发 | 提示用户连接异常、记录错误日志 |
3. 服务端响应规范
SSE要求服务端返回特定的HTTP响应头和数据格式:
Content-Type: text/event-stream # 声明SSE流式数据格式
Cache-Control: no-cache # 禁止缓存,保证数据实时性
Connection: keep-alive # 声明长连接数据体格式必须以data:开头,两个换行符\n\n结束:
data: {"message": "Current time is 10:30:00"}\n\n四、SSE实战Demo说明
文章提供了原生HTML前端 + Node.js Express后端的完整可运行示例,无需任何框架即可验证SSE效果:
1. 前端实现逻辑
- 首先检测浏览器是否支持
window.EventSource,不支持则抛出错误 - 通过
new EventSource('http://localhost:8088/sse/')建立SSE连接 - 监听
open/message/error事件,收到数据后动态创建<li>元素插入页面
<script>
if (!!window.EventSource) {
const source = new EventSource('http://localhost:8088/sse/');
source.onopen = () => console.log("SSE连接已建立");
source.onmessage = (event) => {
const data = JSON.parse(event.data);
const li = document.createElement("li");
li.innerHTML = data.message;
document.getElementById("ul").appendChild(li);
};
source.onerror = () => console.log("SSE连接中断");
} else {
throw new Error("当前浏览器不支持SSE");
}
</script>2. 后端实现逻辑(Node.js Express)
- 配置跨域支持,允许前端页面访问
- 设置SSE要求的响应头
- 通过
setInterval每秒向客户端推送一次当前时间
const express = require('express');
const app = express();
const port = 8088;
// 跨域配置
app.all("*", (req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
req.method === 'OPTIONS' ? res.sendStatus(200) : next();
});
// SSE接口
app.get("/sse", (req, res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
setInterval(() => {
const data = { message: `Current time is ${new Date().toLocaleTimeString()}` };
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 1000);
});
app.listen(port, () => console.log(`服务启动成功:http://localhost:${port}`));五、关键结论与注意事项
- 选型优先级:能用SSE解决的单向推送需求,不要用更复杂的WebSocket;需要双向通信再选WebSocket;轮询仅作为兜底方案。
兼容性提醒:
- SSE和WebSocket现代浏览器兼容性均较好
- ❌ IE浏览器不支持SSE
- ❌ 微信小程序不支持SSE
- 开发小技巧:轮询虽然不推荐用于生产环境,但开发调试时实现成本极低,适合快速验证逻辑。