SSE(Server-Sent Events,服务器发送事件)是一种基于 HTTP 的单向通信协议,允许服务器主动、持续地向客户端推送数据,而无需客户端反复发起请求
核心特点
- 单项通信:仅服务器向客户端实时推送符合条件的信息;
- 基于HTTP:无需额外协议,兼容性好;
- 自动重连:原生支持断开后自动建立连接
如何实现SSE
核心原理
- 服务器端:设置响应头为content0Type:text/event-stream,保持HTTP连接不关闭,持续输出符合SSE格式的文本;
- 客户端:使用浏览器原生的EventSource对象连接服务器,监听消息事件
服务端实现
- 技术栈:Express + node.js
js
const express = require('express')
const app = express();
const port = 3000;
// 允许跨域(方便前端页面访问)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// 静态文件托管(如果前端页面和后端同目录,可通过 http://localhost:3000/index.html 访问)
app.use(express.static(__dirname));
//设置SSE响应头
app.get('/api/sse',(req,res)=>{
res.status(200);
res.set({
'Content-Type': 'text/event-stream', // 核心:声明事件流格式
'Cache-Control': 'no-cache', // 禁止缓存
'Connection': 'keep-alive', // 保持长连接
})
console.log('客户端建立了 SSE 连接');
//每个一秒发送一次数据
const interval = setInterval(() => {
try {
// 1. 推送普通消息(默认 event: message)
// Express 5.x 要求 res.write() 入参为 Buffer 类型,需手动转换
const randomData = {
time: new Date().toLocaleTimeString(),
value: Math.floor(Math.random() * 100), // 0-99 随机数
type: 'normal'
};
const normalMessage = `data: ${JSON.stringify(randomData)}\n\n`;
res.write(Buffer.from(normalMessage)); // 关键适配:转为 Buffer
// 2. 推送自定义事件(比如 "alert" 事件)
if (randomData.value > 80) { // 模拟阈值告警
const alertData = {
time: new Date().toLocaleTimeString(),
msg: `数值超过阈值!当前值:${randomData.value}`,
level: 'warning'
};
const alertMessage = `event: alert\ndata: ${JSON.stringify(alertData)}\n\n`;
res.write(Buffer.from(alertMessage)); // 关键适配:转为 Buffer
}
} catch (err) {
console.error('推送数据失败:', err);
clearInterval(interval);
res.end();
}
}, 1000);
// 客户端断开连接时清理定时器,释放资源
req.on('close', () => {
console.log('客户端断开 SSE 连接');
clearInterval(interval);
res.end();
});
})
// 启动服务器
app.listen(port, () => {
console.log(`SSE 演示服务器(Express 5.x)已启动:http://localhost:${port}`);
console.log('请打开 http://localhost:3000/index.html 查看效果');
});
代码详解
- 基础依赖和服务器初始化
js
const express = require('express')
const app = express();
const port = 3000;
- 引入
express然后初始化一个express实例 - 定义服务器监听的端口号
js
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
- 设置允许跨域,允许不同源进行请求,方便后续操作
js
app.use(express.static(__dirname));
- 这是一个静态文件托管中间件
SSE 核心接口
js
app.get('/api/sse',(req,res)=>{
res.status(200);
res.set({
'Content-Type': 'text/event-stream', // 核心:声明事件流格式
'Cache-Control': 'no-cache', // 禁止缓存
'Connection': 'keep-alive', // 保持长连接
})
console.log('客户端建立了 SSE 连接');
//每个一秒发送一次数据
const interval = setInterval(() => {
try {
// 1. 推送普通消息(默认 event: message)
// Express 5.x 要求 res.write() 入参为 Buffer 类型,需手动转换
const randomData = {
time: new Date().toLocaleTimeString(),
value: Math.floor(Math.random() * 100), // 0-99 随机数
type: 'normal'
};
const normalMessage = `data: ${JSON.stringify(randomData)}\n\n`;
res.write(Buffer.from(normalMessage)); // 关键适配:转为 Buffer
// 2. 推送自定义事件(比如 "alert" 事件)
if (randomData.value > 80) { // 模拟阈值告警
const alertData = {
time: new Date().toLocaleTimeString(),
msg: `数值超过阈值!当前值:${randomData.value}`,
level: 'warning'
};
const alertMessage = `event: alert\ndata: ${JSON.stringify(alertData)}\n\n`;
res.write(Buffer.from(alertMessage)); // 关键适配:转为 Buffer
}
} catch (err) {
console.error('推送数据失败:', err);
clearInterval(interval);
res.end();
}
}, 1000);
// 客户端断开连接时清理定时器,释放资源
req.on('close', () => {
console.log('客户端断开 SSE 连接');
clearInterval(interval);
res.end();
});
})
-
res.status(200):首先设置HTTP响应状态码 -
res.set(...):批量设置响应头,这 3 个是 SSE 的核心头: -
text/event-stream:告诉客户端这是 SSE 事件流,不是普通 JSON/HTML; -
no-cache:禁用缓存,确保客户端每次收到的都是实时数据; -
keep-alive:让 HTTP 连接不关闭,服务器能持续推送数据 -
interval:设置定时器,进行定时推送 -
setInterval(..., 1000):创建定时器,每 1000ms(1 秒)执行一次推送逻辑;
-
try/catch:捕获推送过程中的异常(如客户端断开连接导致res.write()失败); -
普通消息格式 :
data: ${JSON.stringify(randomData)}\n\ndata:是 SSE 固定前缀,后面跟具体数据;JSON.stringify():将对象转为字符串(SSE 只能传输文本);\n\n:必须结尾,是 SSE 消息的结束标识;
-
Express 5.x 适配 :
Buffer.from(normalMessage)- Express 5.x 对
res.write()的入参类型做了严格限制,必须是 Buffer / 字符串(部分版本仅支持 Buffer),这里手动转 Buffer 确保兼容;
- Express 5.x 对
-
自定义事件(alert) :
- 格式:
event: alert\ndata: ...\n\n,event: alert声明事件名为alert; - 客户端需通过
eventSource.addEventListener('alert', ...)监听,而非默认的onmessage; - 作用:区分不同类型的消息(如普通数据、告警、通知),方便前端分类处理。
- 格式:
js
req.on('close', () => {
console.log('客户端断开 SSE 连接');
clearInterval(interval);
res.end();
});
-
req.on('close', ...):监听客户端断开连接事件(如前端关闭页面、主动关闭EventSource); -
clearInterval(interval):销毁定时器,否则定时器会一直运行,导致服务器内存泄漏; -
res.end():主动关闭 HTTP 连接,释放服务器资源。
javascript
app.listen(port, () => {
console.log(`SSE 演示服务器(Express 5.x)已启动:http://localhost:${port}`);
console.log('请打开 http://localhost:3000/index.html 查看效果');
});
-
app.listen(port, ...):启动服务器,监听 3000 端口; -
回调函数:服务器启动成功后打印提示信息,方便开发者知道访问地址
客户端实现
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SSE 实时推送演示(Express 5.x)</title>
<style>
body { padding: 20px; font-family: Arial; }
.normal { color: #333; margin: 5px 0; }
.alert { color: #e63946; background: #fef0f0; padding: 8px; margin: 5px 0; border-radius: 4px; }
#status { color: #2a9d8f; font-weight: bold; margin-bottom: 10px; }
</style>
</head>
<body>
<h1>SSE 实时数据推送演示(Express 5.x)</h1>
<div id="status">连接状态:未连接</div>
<h3>普通数据推送</h3>
<div id="normal-data"></div>
<h3>告警数据推送</h3>
<div id="alert-data"></div>
<script>
// 获取页面元素
const statusEl = document.getElementById('status');
const normalDataEl = document.getElementById('normal-data');
const alertDataEl = document.getElementById('alert-data');
// 检查浏览器是否支持 SSE
if (!!window.EventSource) {
// 创建 SSE 连接
const es = new EventSource('http://localhost:3000/api/sse');
// 连接成功(open 事件)
es.onopen = () => {
statusEl.textContent = '连接状态:已建立 SSE 连接';
statusEl.style.color = '#2a9d8f';
};
// 接收普通消息(message 事件)
es.onmessage = (e) => {
const data = JSON.parse(e.data);
const item = document.createElement('div');
item.className = 'normal';
item.textContent = `[${data.time}] 随机数值:${data.value}`;
normalDataEl.prepend(item);
// 只保留最新 10 条
if (normalDataEl.children.length > 10) {
normalDataEl.removeChild(normalDataEl.lastChild);
}
};
// 接收自定义 alert 事件
es.addEventListener('alert', (e) => {
const data = JSON.parse(e.data);
const item = document.createElement('div');
item.className = 'alert';
item.textContent = `[${data.time}] ${data.msg}`;
alertDataEl.prepend(item);
// 只保留最新 5 条告警
if (alertDataEl.children.length > 5) {
alertDataEl.removeChild(alertDataEl.lastChild);
}
});
// 连接错误(error 事件)
es.onerror = (e) => {
if (es.readyState === EventSource.CLOSED) {
statusEl.textContent = '连接状态:连接已关闭(将自动重试)';
statusEl.style.color = '#e9c46a';
} else {
statusEl.textContent = '连接状态:出错 - ' + (e.message || '未知错误');
statusEl.style.color = '#e63946';
}
};
// 页面关闭时断开连接
window.onbeforeunload = () => {
es.close();
};
} else {
statusEl.textContent = '连接状态:你的浏览器不支持 SSE!';
statusEl.style.color = '#e63946';
}
</script>
</body>
</html>
在这里我不对布局进行讲解,我们来看主要的SSE接收部分
js
if (!!window.EventSource) {
// 创建 SSE 连接
const es = new EventSource('http://localhost:3000/api/sse');
// 连接成功(open 事件)
es.onopen = () => {
statusEl.textContent = '连接状态:已建立 SSE 连接';
statusEl.style.color = '#2a9d8f';
};
// 接收普通消息(message 事件)
es.onmessage = (e) => {
const data = JSON.parse(e.data);
const item = document.createElement('div');
item.className = 'normal';
item.textContent = `[${data.time}] 随机数值:${data.value}`;
normalDataEl.prepend(item);
// 只保留最新 10 条
if (normalDataEl.children.length > 10) {
normalDataEl.removeChild(normalDataEl.lastChild);
}
};
// 接收自定义 alert 事件
es.addEventListener('alert', (e) => {
const data = JSON.parse(e.data);
const item = document.createElement('div');
item.className = 'alert';
item.textContent = `[${data.time}] ${data.msg}`;
alertDataEl.prepend(item);
// 只保留最新 5 条告警
if (alertDataEl.children.length > 5) {
alertDataEl.removeChild(alertDataEl.lastChild);
}
});
// 连接错误(error 事件)
es.onerror = (e) => {
if (es.readyState === EventSource.CLOSED) {
statusEl.textContent = '连接状态:连接已关闭(将自动重试)';
statusEl.style.color = '#e9c46a';
} else {
statusEl.textContent = '连接状态:出错 - ' + (e.message || '未知错误');
statusEl.style.color = '#e63946';
}
};
// 页面关闭时断开连接
window.onbeforeunload = () => {
es.close();
};
} else {
statusEl.textContent = '连接状态:你的浏览器不支持 SSE!';
statusEl.style.color = '#e63946';
}
- 首先对浏览器的兼容性进行检测,检测是否兼容
EventSource,如果不兼容,就执行如下内容
js
statusEl.textContent = '连接状态:你的浏览器不支持 SSE!';
statusEl.style.color = '#e63946';
- 否则,通过
open方法在连接成功时,对页面状态进行更改
- 然后通过message来接收普通消息

js
es.onmessage = (e) => {
const data = JSON.parse(e.data);
const item = document.createElement('div');
item.className = 'normal';
item.textContent = `[${data.time}] 随机数值:${data.value}`;
normalDataEl.prepend(item);
// 只保留最新 10 条
if (normalDataEl.children.length > 10) {
normalDataEl.removeChild(normalDataEl.lastChild);
}
};
-
onmessage事件:接收后端推送的默认事件(event: message) ; -
e.data:后端推送的文本数据(JSON 字符串),需用JSON.parse转为 JavaScript 对象; -
prepend(item):将新消息添加到父元素的最前面,实现 "最新消息置顶"; -
条数限制:避免页面积累过多消息导致卡顿,只保留最新 10 条。
js
// 接收自定义 alert 事件
es.addEventListener('alert', (e) => {
const data = JSON.parse(e.data);
const item = document.createElement('div');
item.className = 'alert';
item.textContent = `[${data.time}] ${data.msg}`;
alertDataEl.prepend(item);
// 只保留最新 5 条告警
if (alertDataEl.children.length > 5) {
alertDataEl.removeChild(alertDataEl.lastChild);
}
});
-
addEventListener('alert', ...):监听后端推送的自定义事件(event: alert) ; -
区别于
onmessage:onmessage只能监听默认事件,自定义事件必须用addEventListener; -
条数限制:告警消息更重要,只保留最新 5 条,避免干扰。
js
// 连接错误(error 事件)
es.onerror = (e) => {
if (es.readyState === EventSource.CLOSED) {
statusEl.textContent = '连接状态:连接已关闭(将自动重试)';
statusEl.style.color = '#e9c46a';
} else {
statusEl.textContent = '连接状态:出错 - ' + (e.message || '未知错误');
statusEl.style.color = '#e63946';
}
};
-
onerror事件:连接出错(如后端服务挂了、网络断开)时触发; -
es.readyState:SSE 连接状态,EventSource.CLOSED表示连接已关闭; -
逻辑:区分 "连接关闭(会自动重试)" 和 "未知错误",给出不同提示,提升用户体验。
js
window.onbeforeunload = () => {
es.close();
};
-
window.onbeforeunload:页面关闭 / 刷新前触发; -
es.close():主动关闭 SSE 连接,避免后端长连接残留(后端会监听到req.close事件,清理定时器)。
效果
