在开发过程中,我们遇到了这样一种场景:需要提交一项复杂计算任务,该任务可能耗时十几分钟甚至几十分钟之久。在计算过程中,结果会持续动态生成,然而每次生成结果的具体时间点却无法精准预估。与此同时,客户端又期望能够实时展示这些计算数据。针对这一需求,我们自然而然的想到了Websocket。哦,不是!是Server-Sent Events (SSE)。
一、SSE 是什么
Server-Sent Events (SSE) 是一种允许服务器单向推送实时更新到客户端(如浏览器)的 Web API。与传统的客户端主动拉取数据模式(如短轮询或长轮询)不同,SSE 让服务器在数据有变化时主动将数据发送给客户端,极大地减少了不必要的请求和网络开销,显著提升了用户体验。
SSE 构建在 HTTP 协议之上,使用text/event-stream内容类型来传输数据。客户端通过EventSource接口建立与服务器的持久连接,一旦连接建立,服务器即可持续向客户端发送事件流数据。此外,SSE 还具备自动重连机制,若连接中断,客户端会自动尝试重新连接到服务器,确保数据传输的连续性。
二、SSE 的工作原理
-
建立连接:客户端通过EventSource对象发起一个 HTTP GET 请求到服务器指定的 SSE 端点,请求头中包含Accept: text/event-stream,表明客户端期望接收 SSE 格式的数据。
-
服务器响应:服务器接收到请求后,设置响应头Content-Type: text/event-stream,以告知客户端这是一个 SSE 响应,并保持连接打开。
-
数据推送:服务器按照 SSE 格式,持续向客户端发送数据块。每个数据块以data:开头,包含实际的数据内容,以两个换行符\n\n作为消息结束的标志。例如:
data:这是第一条消息\n\n
data:第二条消息\n\n
4. 客户端处理:客户端通过onmessage事件或addEventListener监听接收到的数据。当有新数据到达时,触发相应的事件处理函数,对接收到的数据进行解析和处理。
三、SSE 的应用场景
- 实时通知:向用户发送系统警报、新消息提醒、任务完成通知等,确保用户及时知晓重要信息。
- 新闻和社交媒体推送:实时更新新闻文章、社交媒体动态、用户评论等,让用户随时掌握最新资讯。
- 实时监控:用于监控系统,如服务器状态监控、网络流量监控、工业设备状态监控等,实时反馈监控数据的变化。
- 体育赛事直播:推送比赛比分、球员动态、赛事进展等信息,为观众提供实时的赛事体验。
- 在线协作:在多人在线协作工具中,实时同步用户的操作,如文档编辑、项目管理等,提升协作效率。
四、SSE 的代码示例
废话少说,先看效果
(一)服务端(以 Node.js 和 Express 为例,向客户端推送一篇文学巨作)
- 安装必要的依赖包:
npm install express
2. 编写服务端代码:
设置SSE的响应头为text/event-stream,先推送一条初始消息,然后开始定时推送。
app.get('/events', (req, res) => {
// 设置SSE响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
// 保存客户端连接
const clientId = Date.now() + '-' + Math.random().toString(36).substr(2, 9);
clients.set(clientId, res);
// 发送初始连接消息
res.write(`data: ${JSON.stringify({ type: 'CONNECTED', message: '开始推送《离骚》' })}\n\n`);
// 监听客户端断开连接
req.on('close', () => {
clients.delete(clientId);
console.log(`Client ${clientId} disconnected`);
});
});
每1秒向客户端推送一行诗句
// 定时向所有连接的客户端推送《离骚》诗句
let currentLine = 0;
setInterval(() => {
if (clients.size > 0 && currentLine < liSaoText.length) {
const line = liSaoText[currentLine];
const message = {
type: 'POEM_LINE',
content: line
};
clients.forEach((clientRes) => {
clientRes.write(`data: ${JSON.stringify(message)}\n\n`);
});
currentLine++;
// 如果已经推送完所有诗句,发送完成消息
if (currentLine >= liSaoText.length) {
const completeMessage = {
type: 'COMPLETE'
};
clients.forEach((clientRes) => {
clientRes.write(`data: ${JSON.stringify(completeMessage)}\n\n`);
});
}
}
}, 1000); // 每1秒推送一行
(二)客户端(HTML 和 JavaScript)
客户端通过onmessage接收服务端传过来的数据,并渲染到页面上。
connect() {
if (this.isConnected) return;
this.eventSource = new EventSource('/events');
this.eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleMessage(data);
} catch (e) {
console.error('Error parsing message data:', e);
}
};
this.eventSource.onerror = (err) => {
console.error('SSE连接错误:', err);
this.setConnected(false);
};
this.setConnected(true);
}
五、SSE 与 WebSocket 的比较
- 通信方向:SSE 仅支持服务器向客户端的单向通信,适用于大部分数据由服务器推送的场景;WebSocket 则支持双向通信,客户端和服务器均可随时发送数据,适用于需要实时交互的场景,如在线聊天、多人游戏等。
- 协议复杂性:SSE 基于 HTTP 协议,实现相对简单,对服务器和客户端的资源消耗较少;WebSocket 使用自定义协议,需要进行握手等复杂操作,实现相对复杂,但功能更强大。
- 浏览器兼容性:主流浏览器(如 Chrome、Firefox、Safari、Edge)均支持 SSE 和 WebSocket,但在一些旧版本浏览器中,可能需要额外的 polyfill 来支持。
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(服务器→客户端) | 双向(客户端↔服务器) |
| 协议基础 | 基于 HTTP(长连接) | 独立协议(握手依赖 HTTP,之后基于 TCP) |
| 连接持久性 | 持久连接,断开后需客户端主动重连 | 持久连接,支持自动重连(需手动实现逻辑) |
| 数据格式 | 仅支持文本(默认text/event-stream) | 支持文本、二进制(如 Blob、ArrayBuffer) |
| 浏览器兼容性 | 主流浏览器支持(IE 不支持) | 主流浏览器支持(IE10 + 支持) |
| 连接限制 | 受 HTTP 并发连接限制(如同一域名 6 个) | 无 HTTP 连接限制,可建立多个连接 |
| 自动重连 | EventSource内置重连机制 | 需手动实现重连逻辑 |
| 头部开销 | 每次数据推送带 HTTP 头部(开销较大) | 仅握手阶段有 HTTP 头部,后续传输无额外开销 |
| 适用场景 | 服务器主动推送(如新闻通知、股价更新) | 双向实时交互(如聊天、在线游戏、协作工具) |
六、总结
Server-Sent Events (SSE) 为实现实时数据推送提供了一种简单、高效且易于使用的解决方案。通过减少不必要的请求和网络开销,SSE 能够显著提升应用程序的性能和用户体验。在实际应用中,应根据具体需求和场景,合理选择 SSE 或 WebSocket 等技术,以实现最佳的实时通信效果。你们说,Server-Sent Events香不香