SSE(Server-Sent Events) ------ 它是一种服务器向客户端单向实时推送的通信协议,基于 HTTP 协议实现,无需像 WebSocket 那样建立独立连接,适合需服务器主动推送、客户端仅接收的场景(如实时通知、行情更新)
核心概念
- 单向通信:仅支持服务器向客户端主动推送数据,客户端无法通过 SSE 向服务器发送数据(需搭配 HTTP 接口补充双向通信)。
- 基于 HTTP 协议:复用 HTTP 连接,无需额外创建新协议(如 WebSocket 的 ws://),兼容性更好,服务器部署更简单。
- 协议标识:客户端通过 EventSource API 建立连接,请求头指定 Accept: text/event-stream,服务器以特定格式(文本流)响应。
- 核心特性 :
- 自动重连:客户端连接断开后,默认会自动重试(可配置重连时间);
- 断线重连时可恢复历史数据(通过 Last-Event-ID 实现);
- 支持自定义事件类型,不止于默认的 message 事件;
- 仅支持文本数据传输(二进制数据需编码为 Base64)。
- 生命周期:客户端建立连接 → 服务器持续推送文本流 → 客户端断开连接(主动 / 超时)。
基本用法
1. 前端(浏览器)核心 API:EventSource
浏览器原生支持 EventSource 对象,无需依赖第三方库,核心方法与事件如下:
js
// 前端:SSE 客户端实现
let eventSource = null;
// 1. 建立 SSE 连接
function initSSE() {
// 检测浏览器支持(所有现代浏览器均支持,IE 不支持)
if (!window.EventSource) {
console.warn('当前浏览器不支持 SSE(建议使用 Chrome/Firefox/Edge)');
return;
}
// 建立连接(URL 为后端 SSE 接口,支持跨域,需服务器配置 CORS)
eventSource = new EventSource('http://localhost:8080/sse', {
withCredentials: true // 跨域时携带 Cookie(如需)
});
// 2. 连接建立成功事件
eventSource.onopen = function() {
console.log('SSE 连接已建立');
// 客户端如需向服务器发数据,需通过 HTTP 接口(如 POST)
// 示例:发送用户登录状态到服务器(SSE 本身不支持客户端→服务器)
fetch('http://localhost:8080/login', {
method: 'POST',
body: JSON.stringify({ username: '前端用户' }),
headers: { 'Content-Type': 'application/json' }
});
};
// 3. 接收服务器默认事件(事件类型为 message)
eventSource.onmessage = function(event) {
// event.data 为服务器推送的文本数据
const data = JSON.parse(event.data);
console.log('收到默认消息:', data);
handleServerData('message', data);
};
// 4. 接收服务器自定义事件(如 "notice" 事件)
eventSource.addEventListener('notice', function(event) {
const data = JSON.parse(event.data);
console.log('收到自定义 notice 事件:', data);
handleServerData('notice', data);
});
// 5. 接收服务器行情事件(如 "stock" 事件)
eventSource.addEventListener('stock', function(event) {
const data = JSON.parse(event.data);
console.log('收到股票行情:', data);
handleServerData('stock', data);
});
// 6. 连接错误事件(如网络中断、服务器异常)
eventSource.onerror = function(error) {
console.error('SSE 连接错误:', error);
// 错误时会自动重连,可手动处理(如提示用户)
if (eventSource.readyState === EventSource.CLOSED) {
console.log('SSE 连接已关闭,等待自动重连...');
}
};
// 7. 连接关闭事件(仅主动关闭时触发,自动重连时不触发)
eventSource.onclose = function() {
console.log('SSE 连接已主动关闭');
};
}
// 处理服务器推送的数据
function handleServerData(eventType, data) {
switch (eventType) {
case 'message':
// 渲染普通消息
const msgDom = document.createElement('div');
msgDom.textContent = `系统消息:${data.content}`;
document.getElementById('sse-container').appendChild(msgDom);
break;
case 'notice':
// 渲染通知(弹窗)
alert(`【实时通知】${data.title}:${data.content}`);
break;
case 'stock':
// 更新行情数据
document.getElementById('stock-price').textContent = data.price;
document.getElementById('stock-time').textContent = data.timestamp;
break;
}
}
// 主动关闭 SSE 连接
function closeSSE() {
if (eventSource) {
eventSource.close(); // 关闭后不会自动重连
eventSource = null;
}
}
// 页面加载时建立连接
window.addEventListener('load', initSSE);
// 页面卸载时关闭连接
window.addEventListener('beforeunload', closeSSE);
2. 后端(Node.js)SSE 服务实现(基于 Express)
SSE 服务器需持续向客户端发送文本流,需设置特定响应头和数据格式,这里以 Node.js + Express 为例(需安装 express:npm install express):
js
// 后端:Node.js SSE 服务(server.js)
const express = require('express');
const app = express();
const port = 8080;
// 跨域配置(SSE 支持跨域,需允许特定域名)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000'); // 允许前端域名
res.setHeader('Access-Control-Allow-Credentials', 'true'); // 允许携带 Cookie
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
});
// 解析 JSON 请求体(用于接收客户端 HTTP 接口数据)
app.use(express.json());
// 存储所有 SSE 连接(用于广播消息)
let sseClients = new Set();
// 1. SSE 核心接口(客户端通过 EventSource 连接此接口)
app.get('/sse', (req, res) => {
// 设置 SSE 响应头(关键!)
res.setHeader('Content-Type', 'text/event-stream'); // 声明为事件流
res.setHeader('Cache-Control', 'no-cache'); // 禁用缓存
res.setHeader('Connection', 'keep-alive'); // 保持连接
res.flushHeaders(); // 立即发送响应头(避免缓存)
// 存储当前连接(用于后续推送)
sseClients.add(res);
console.log('新 SSE 客户端连接,当前连接数:', sseClients.size);
// 客户端断开连接时移除
req.on('close', () => {
sseClients.delete(res);
console.log('SSE 客户端断开连接,当前连接数:', sseClients.size);
});
// 初始推送(连接成功后发送欢迎消息)
sendSSEMessage(res, 'message', { content: '欢迎连接 SSE 实时推送服务' });
});
// 2. 接收客户端数据的 HTTP 接口(SSE 单向,需补充此接口实现双向通信)
app.post('/login', (req, res) => {
const { username } = req.body;
console.log('用户登录:', username);
// 广播登录通知给所有 SSE 客户端
broadcastSSEMessage('notice', {
title: '登录通知',
content: `${username} 已上线`,
timestamp: new Date().toLocaleTimeString()
});
res.json({ code: 200, msg: '登录成功' });
});
// 3. 模拟服务器定时推送数据(如股票行情)
setInterval(() => {
if (sseClients.size === 0) return;
// 模拟股票价格波动
const stockData = {
code: '600000',
name: '浦发银行',
price: (10 + Math.random() * 2).toFixed(2),
timestamp: new Date().toLocaleTimeString()
};
// 向所有客户端推送行情
broadcastSSEMessage('stock', stockData);
}, 3000); // 每 3 秒推送一次
// 工具函数:向单个客户端发送 SSE 消息
function sendSSEMessage(res, eventType = 'message', data) {
// SSE 数据格式规范(必须严格遵守):
// 1. 每行以 "\n" 结尾;
// 2. 事件类型:event: 事件名\n;
// 3. 数据:data: 字符串数据\n;
// 4. 可选 ID:id: 消息ID\n;
// 5. 结束符:"\n\n"(必须两个换行)
const message = `event: ${eventType}\n` +
`data: ${JSON.stringify(data)}\n` +
`id: ${Date.now()}\n` + // 用于断线重连时恢复数据
`\n`; // 消息结束符
res.write(message); // 发送消息(持续写入流)
}
// 工具函数:向所有客户端广播 SSE 消息
function broadcastSSEMessage(eventType, data) {
sseClients.forEach(res => {
// 确保连接未关闭
if (!res.finished) {
sendSSEMessage(res, eventType, data);
}
});
}
// 启动服务器
app.listen(port, () => {
console.log(`SSE 服务启动成功,地址:http://localhost:${port}`);
});
3. 前端 HTML 结构(实时行情 + 通知示例)
html
<!DOCTYPE html>
<html>
<body>
<h3>SSE 实时推送示例(股票行情 + 通知)</h3>
<div>
<h4>浦发银行(600000)</h4>
<p>当前价格:<span id="stock-price">--</span></p>
<p>更新时间:<span id="stock-time">--</span></p>
</div>
<div id="sse-container" style="margin-top: 20px; padding: 10px; border: 1px solid #ccc;"></div>
<button onclick="closeSSE()" style="margin-top: 10px;">关闭 SSE 连接</button>
<script src="前端 SSE 核心逻辑.js"></script>
</body>
</html>
核心 API
前端 EventSource 核心 API
| 属性 / 方法 | 说明 |
|---|---|
| new EventSource(url[, options]) | 创建 SSE 连接,url 为后端接口,options 可选(如 withCredentials: true 跨域带 Cookie) |
| onopen | 连接建立成功时触发 |
| onmessage | 接收服务器默认 message 事件时触发(event.data 为消息内容) |
| onerror | 连接错误或断开时触发(自动重连) |
| onclose | 主动调用 close() 后触发(不会自动重连) |
| addEventListener(eventType, callback) | 监听服务器自定义事件(如 notice、stock) |
| close() | 主动关闭 SSE 连接(关闭后需重新创建 EventSource 实例才能重连) |
| readyState | 连接状态:0(CONNECTING,连接中)、1(OPEN,已连接)、2(CLOSED,已关闭) |
数据格式详解
服务器 SSE 响应格式规范(关键!)
服务器必须以特定文本格式响应,否则客户端无法解析,核心格式如下:
js
# 完整消息格式(每行以 \n 结尾,消息以 \n\n 结束)
event: 自定义事件名\n # 可选,默认 message
data: 文本数据(可多行)\n # 必选,支持 JSON 字符串
id: 消息ID\n # 可选,用于断线重连时通过 Last-Event-ID 恢复
retry: 重连时间(毫秒)\n # 可选,指定客户端自动重连间隔(默认 3000ms)
\n\n # 消息结束符(必须两个换行)
# 示例 1:默认 message 事件
data: {"content": "这是默认事件消息"}\n
id: 1699999999999\n
\n\n
# 示例 2:自定义 notice 事件
event: notice\n
data: {"title": "通知", "content": "实时通知内容"}\n
retry: 5000\n
\n\n
# 示例 3:多行数据(data 可分多行)
data: {\n
data: "content": "多行消息",\n
data: "time": "2024-01-01"\n
data: }\n
\n\n
典型应用场景
- 实时通知:如系统公告、订单状态更新、消息提醒(无需客户端向服务器反馈);
- 实时数据推送:如股票 / 加密货币行情、体育赛事比分、实时监控数据(服务器持续更新,客户端仅展示);
- 日志流展示:如后台系统操作日志、构建日志实时输出(服务器推送日志,客户端实时渲染);
- 新闻 / 资讯推送:如实时新闻、活动更新(服务器有新内容时主动推送给客户端)。
进阶用法与优化
1. 断线重连与历史数据恢复
SSE 原生支持通过 Last-Event-ID 恢复历史数据,客户端重连时会在请求头携带上次接收的 id,服务器可据此推送未接收的消息:
js
// 后端:获取客户端 Last-Event-ID,恢复历史数据
app.get('/sse', (req, res) => {
const lastEventId = req.headers['last-event-id']; // 客户端重连时携带
if (lastEventId) {
console.log('客户端重连,上次消息 ID:', lastEventId);
// 模拟恢复历史数据(实际场景从数据库/缓存查询)
const historyData = { content: '恢复断线前未接收的消息' };
sendSSEMessage(res, 'message', historyData);
}
// ... 其余逻辑不变
});
2. 自定义重连时间
服务器可通过 retry 字段指定客户端自动重连间隔,客户端也可通过 eventSource.retry 读取(但无法修改,需服务器返回):
js
// 服务器响应时添加 retry 字段(客户端重连间隔改为 5 秒)
const message = `event: notice\n` +
`data: {"content": "自定义重连间隔"}\n` +
`retry: 500</doubaocanvas>