深入浅出 SSE:实现服务器向客户端的单向实时通信
文章目录
- **1. 引言:什么是实时通信?**
- **2. SSE 技术详解**
- **2.1 核心概念**
- **2.2 通信流程与协议格式**
- **3. SSE 的优缺点**
- **3.1 优点**
- **3.2 缺点**
- **4. 适用场景**
- **5. 实战代码示例**
- **5.1 服务器端**
- **5.2 客户端**
- **6. 与 WebSocket 的对比**
- **7. 总结**
1. 引言:什么是实时通信?
在传统的 Web 应用中,客户端(浏览器)主动向服务器发起请求,服务器处理请求后返回响应,然后连接关闭。这种“一问一答”的模式(如 Ajax)对于需要实时获取数据的场景(如实时消息、股票行情、监控报警)来说,效率非常低下。客户端只能通过频繁“轮询”来模拟实时效果,这会给服务器带来巨大压力。
为了解决这个问题,HTML5 引入了一系列实时 Web 技术,其中 SSE 就是一个非常重要且易于使用的标准。
2. SSE 技术详解
2.1 核心概念
SSE 的全称是 Server-Sent Events,即服务器发送事件。它是一种允许服务器主动向客户端推送数据的 Web 技术。
- 基于 HTTP:SSE 完全基于标准的 HTTP 协议,无需额外的协议。
- 单向通信:它的核心是服务器到客户端的单向数据流。客户端发起连接,服务器可以随时通过这个连接发送数据。
- 文本协议:SSE 默认传输的是 UTF-8 编码的文本数据。
- 长连接:客户端与服务器会建立一个长时间保持的连接,使得服务器可以随时“分块”发送数据。
- 自动重连:SSE 内置了自动重连机制,当连接意外断开时,浏览器会自动尝试重新连接。
2.2 通信流程与协议格式
为了更直观地理解 SSE 的工作流程,我们可以用下面的流程图来展示:
flowchart TDA[客户端 Client] -->|1. 创建 EventSource<br>发起 GET 请求| B[服务器 Server]B -->|2. 设置响应头<br>Content-Type: text/event-stream| Aloop 3. 持续数据推送B -->|Data: 消息内容<br>event: 事件类型<br>id: 消息ID<br><br>(以两个换行结束)| AendA -->|4. 监听 onmessage/onerror 等事件| AB -->|5. 连接关闭或超时| AA -->|6. 自动重连| B
一个标准的 SSE 数据流不是任意文本,它必须遵循特定的格式。这个流程图中服务器返回的数据格式,具体如下:
服务器响应体示例:
event: userConnected
data: {"username": "Alice", "time": "10:30:00"}data: 这是一条普通消息
data: 这是消息的延续部分id: 12345
event: chat
data: {"from": "Bob", "text": "Hello, World!"}: 这是一条注释行,会被客户端忽略
格式规则:
- 每一行数据由一个字段名、一个冒号和空格、以及一个字段值组成。
data:最重要的字段,表示消息的数据内容。如果一条消息有多个data行,它们会用换行符连接成一个内容。event:可选字段,用于定义事件类型。客户端可以根据不同类型监听不同的事件。如果不指定,默认为message事件。id:可选字段,用于设置消息的 ID。浏览器在重连时,会在 HTTP 头Last-Event-ID中带上最后收到的 ID,服务器可以借此恢复中断期间的消息。retry:可选字段,用于指定浏览器在连接断开后,重新连接前等待的毫秒数。- 消息以两个连续的换行符
\n\n作为结束标志。
3. SSE 的优缺点
3.1 优点
- 简单易用:基于标准 HTTP,客户端使用
EventSourceAPI,几行代码即可实现,无需引入第三方库。 - 自动重连:内置重连机制,提高了应用的健壮性。
- 轻量级:与 WebSocket 相比,协议开销更小,尤其适合简单的文本数据推送。
- 良好的浏览器支持:现代浏览器(除 IE 外)都提供了原生支持。
- 与现有基础设施兼容:无需像 WebSocket 那样进行协议升级,更容易通过防火墙和代理。
3.2 缺点
- 单向通信:最大的限制。客户端无法通过同一个连接向服务器发送数据。如果需要,只能通过额外的 Ajax 请求来完成。
- 仅支持文本数据:虽然可以通过 Base64 编码传输二进制数据,但效率不高。
- 最大连接数限制:浏览器对同一个域名下的 HTTP 连接数有限制(通常是 6 个),SSE 会占用一个长连接。
- 不支持 IE:任何版本的 Internet Explorer 都不支持
EventSource。
4. 适用场景
SSE 非常适合以下“服务器主动,客户端被动接收”的场景:
- 实时通知:社交媒体的新消息通知、邮件客户端的新邮件提醒、系统后台的任务完成通知。
- 实时数据监控:服务器性能监控(CPU、内存)、股票价格实时行情、体育比赛比分直播。
- 新闻/博文更新:在文章页面实时显示新的评论。
- 简单的实时聊天:对于不需要复杂双向交互的聊天室,SSE 用于接收消息,Ajax 用于发送消息,是一个简单有效的组合。
- 数据仪表盘:实时展示业务数据、用户在线统计等。
5. 实战代码示例
下面我们分别用 Node.js 服务器和浏览器客户端来实现一个简单的 SSE 应用。
5.1 服务器端
我们使用 Node.js 和 Express 框架。
// server.js
const express = require('express');
const app = express();// 静态文件服务,用于托管客户端HTML
app.use(express.static('public'));// SSE 路由
app.get('/stream', (req, res) => {// 1. 设置 SSE 相关的响应头res.writeHead(200, {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache','Connection': 'keep-alive','Access-Control-Allow-Origin': '*', // 允许跨域});// 2. 发送一个初始连接成功消息res.write('data: 连接已建立,开始接收实时数据...\n\n');// 3. 模拟每秒发送一条服务器时间消息let messageId = 1;const intervalId = setInterval(() => {const data = {id: messageId++,time: new Date().toLocaleTimeString(),value: Math.random().toFixed(2)};// 发送一条格式化的 SSE 消息// 使用 event 字段定义类型为 'update'res.write(`event: update\n`);res.write(`id: ${data.id}\n`);res.write(`data: ${JSON.stringify(data)}\n\n`);}, 1000);// 4. 当客户端断开连接时,清理定时器req.on('close', () => {console.log('客户端断开连接');clearInterval(intervalId);res.end();});
});const PORT = 3000;
app.listen(PORT, () => {console.log(`SSE 服务器运行在 http://localhost:${PORT}`);
});
5.2 客户端
创建一个 public/index.html 文件。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>SSE 客户端演示</title>
</head>
<body><h1>SSE 实时数据流演示</h1><div id="messages"></div><script>const messagesContainer = document.getElementById('messages');// 1. 创建 EventSource 对象,连接到服务器的 /stream 端点const eventSource = new EventSource('http://localhost:3000/stream');// 2. 监听默认的 'message' 事件(服务器没有指定 event 字段时触发)eventSource.onmessage = function(event) {const message = `[Message]: ${event.data}`;appendMessage(message);};// 3. 监听自定义的 'update' 事件(对应服务器端的 `event: update`)eventSource.addEventListener('update', function(event) {const data = JSON.parse(event.data);const message = `[Update #${data.id}] 时间: ${data.time}, 数值: ${data.value}`;appendMessage(message);});// 4. 监听 'open' 事件,连接成功建立时触发eventSource.onopen = function(event) {console.log('连接已打开');appendMessage('*** 连接已成功建立 ***');};// 5. 监听 'error' 事件eventSource.onerror = function(event) {console.error('SSE 错误:', event);if (eventSource.readyState === EventSource.CLOSED) {appendMessage('*** 连接已关闭 ***');} else {appendMessage('*** 连接发生错误 ***');}};// 工具函数:将消息添加到页面function appendMessage(message) {const p = document.createElement('p');p.textContent = message;messagesContainer.appendChild(p);// 自动滚动到底部messagesContainer.scrollTop = messagesContainer.scrollHeight;}</script>
</body>
</html>
6. 与 WebSocket 的对比
| 特性 | Server-Sent Events | WebSocket |
|---|---|---|
| 通信方向 | 单向 (Server -> Client) | 双向 (Full-Duplex) |
| 协议 | HTTP (文本) | ws/wss (独立的二进制协议) |
| 复杂度 | 低,原生 API 简单 | 高,通常需要库 (如 Socket.IO) |
| 数据格式 | 文本 | 文本、二进制数据 |
| 自动重连 | 内置支持 | 需要手动实现 |
| 浏览器支持 | 良好 (除 IE) | 良好 (包括 IE 10+) |
| 适用场景 | 实时通知、数据流、监控 | 在线游戏、聊天室、协同编辑 |
选择建议:
- 如果你的应用主要是服务器向客户端推送数据,而客户端发送的数据很少(可以通过 Ajax),那么 SSE 是更简单、更轻量的选择。
- 如果你需要频繁的、低延迟的双向通信(如多人在线游戏、实时协作工具),那么 WebSocket 是唯一正确的选择。
7. 总结
SSE 是一项被低估的 Web 实时通信技术。它凭借其简单性、自动重连能力和基于 HTTP 的天然优势,在特定的应用场景下(尤其是数据推送和通知类应用)表现非常出色。虽然它在双向通信和二进制数据传输上存在短板,但当你只需要服务器向客户端发送数据时,SSE 绝对应该是你的首选方案,它能让你用最少的代码实现最稳定的实时功能。
希望这篇文章能帮助你全面理解并上手使用 SSE 技术。

