SSE 协议小白指南:服务器主动"喊"你一嗓子
2026-08-04
如果你做过网页,一定熟悉这种模式:浏览器问服务器要数据,服务器给了,对话就结束了。那如果服务器想主动告诉你”有新消息了”,怎么办?
刷新页面?太蠢。轮询(每隔几秒问一次)?浪费流量。WebSocket?有点重。
这时候就该 SSE(Server-Sent Events,服务器发送事件) 出场了。它专门解决一个简单问题:让服务器能主动、持续地给浏览器推消息,而且实现起来比 WebSocket 简单十倍。
下面用 6 个核心要点 + 6 段可直接运行的代码,带你彻底搞懂 SSE。
1. 它是个”单行道”:只能从服务器流向浏览器
SSE 最大的特点就是单向。浏览器连上之后,服务器可以随时往浏览器发消息,但浏览器不能通过这条通道回传数据(要回传得另开一条 HTTP 请求)。
类比:SSE 像广播电台,服务器是电台,浏览器是收音机。电台可以一直播,收音机只能听,不能对着电台喊话。
例子:一个最简单的 SSE 服务端(Node.js)
const http = require('http');
http.createServer((req, res) => {
// 1. 设置响应头,这是 SSE 的"身份证"
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// 2. 每隔 2 秒推一条消息
let count = 0;
const timer = setInterval(() => {
count++;
res.write(`data: \u7b2c${count}\u6761\u6d88\u606f\uff0c\u65f6\u95f4\uff1a${new Date().toLocaleTimeString()}\n\n`);
if (count >= 5) {
clearInterval(timer);
res.end();
}
}, 2000);
}).listen(3000, () => console.log('SSE \u670d\u52a1\u8dd1\u5728 http://localhost:3000'));关键看第 9 行:Content-Type: text/event-stream,只要带上这个头,浏览器就知道”哦,这是 SSE,我要用 EventSource 来接收”。
2. 消息格式极其死板:必须以 data: 开头
SSE 的消息不是随便写的,它有一套固定格式。每条消息以 data: 打头,以两个换行符 \n\n 结尾。
data: \u8fd9\u662f\u4e00\u6761\u6d88\u606f\n\n
如果你要发多行,每行都要加 data::
data: \u7b2c\u4e00\u884c\ndata: \u7b2c\u4e8c\u884c\n\n
浏览器收到后,会自动把多行拼接成一个字符串,中间用换行符连接。
例子:服务端发多行数据
res.writeHead(200, {
'Content-Type': 'text/event-stream'
});
res.write(`data: {\n`);
res.write(`data: "user": "\u6c5f\u6960",\n`);
res.write(`data: "action": "login",\n`);
res.write(`data: "time": "${new Date().toISOString()}"\n`);
res.write(`data: }\n\n`);浏览器 EventSource 收到的事件中,event.data 会是:
{
"user": "\u6c5f\u6960",
"action": "login",
"time": "2026-08-04T12:34:56.789Z"
}3. 浏览器用 EventSource 接收,三行代码搞定
浏览器原生提供了 EventSource 对象,不需要任何库。
例子:前端接收 SSE
<!DOCTYPE html>
<html>
<body>
<h1>SSE \u5b9e\u65f6\u6d88\u606f</h1>
<ul id="list"></ul>
<script>
// \u53ea\u9700\u8981\u8fd9\u4e00\u884c\uff0c\u8fde\u63a5\u670d\u52a1\u7aef
const es = new EventSource('http://localhost:3000');
// \u6bcf\u6b21\u6536\u5230\u6d88\u606f\u5c31\u89e6\u53d1
es.onmessage = (event) => {
const li = document.createElement('li');
li.textContent = event.data;
document.getElementById('list').appendChild(li);
};
// \u51fa\u9519\u5904\u7406
es.onerror = (err) => {
console.log('\u8fde\u63a5\u65ad\u5f00\uff0c\u6b63\u5728\u91cd\u8fde...');
};
</script>
</body>
</html>就这么简单。new EventSource(url) 创建连接,onmessage 收消息,onerror 处理断开。
4. 支持自定义事件名:不只是 message
默认情况下,所有消息都触发 onmessage。但你可以给消息分类,比如有的叫 price,有的叫 notice。
例子:服务端发送带事件名的消息
// \u63a8\u9001\u80a1\u7968\u4ef7\u683c
res.write(`event: price\n`);
res.write(`data: 38.50\n\n`);
// \u63a8\u9001\u7cfb\u7edf\u901a\u77e5
res.write(`event: notice\n`);
res.write(`data: \u60a8\u7684\u8ba2\u5355\u5df2\u53d1\u8d27\n\n`);前端用 addEventListener 监听不同事件
const es = new EventSource('http://localhost:3000');
es.addEventListener('price', (e) => {
document.getElementById('price').textContent = e.data;
});
es.addEventListener('notice', (e) => {
alert('\u7cfb\u7edf\u901a\u77e5\uff1a' + e.data);
});这样业务逻辑可以分门别类处理,代码更清爽。
5. 自带断线重连:浏览器比你更操心
SSE 有一个非常贴心的机制:如果连接断了,浏览器会自动帮你重连,默认间隔约 3 秒。你什么都不用写。
更妙的是,服务端可以控制重连间隔。比如你发一条 retry: 10000,浏览器就会在断开后等 10 秒再重连。
例子:服务端设置重连间隔 + 消息 ID
// \u544a\u8bc9\u6d4f\u89c8\u5668\uff1a\u65ad\u5f00\u540e 10 \u79d2\u518d\u8bd5
res.write(`retry: 10000\n`);
let id = 0;
setInterval(() => {
id++;
// \u6bcf\u6761\u6d88\u606f\u90fd\u5e26\u4e0a ID\uff0c\u65ad\u7ebf\u540e\u670d\u52a1\u7aef\u53ef\u4ee5\u4ece\u8fd9\u4e2a ID \u540e\u7eed\u4f20
res.write(`id: ${id}\n`);
res.write(`data: \u6d88\u606f\u7f16\u53f7${id}\n\n`);
}, 2000);浏览器重连时,会在请求头里带上 Last-Event-ID: 5,服务端读到后就知道”哦,客户端已经收到第 5 条了,我从第 6 条开始发”,实现断点续传。
6. SSE vs WebSocket:别拿大炮打蚊子
很多人一听到”实时推送”就想到 WebSocket。但 SSE 在单工场景下更轻量。
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 服务器→客户端(单工) | 全双工 |
| 协议 | HTTP(兼容性好) | ws/wss(独立协议) |
| 自动重连 | 705 原生支持 | 74c 需自己写 |
| 消息 ID/断点续传 | 705 原生支持 | 74c 需自己实现 |
| 二进制支持 | 74c 只能传文本 | 705 支持 |
| 实时游戏/双向高频通信 | 不合适 | 合适 |
| 股票推送/日志流/通知 | 完美 | 过重 |
例子:日志实时推送(SSE 比 WebSocket 更合适)
const fs = require('fs');
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
});
// \u5b9e\u65f6\u8ffd\u8e2a app.log \u6587\u4ef6
const stream = fs.createReadStream('/var/log/app.log', { start: fs.statSync('/var/log/app.log').size });
stream.on('data', chunk => {
const lines = chunk.toString().split('\n');
lines.forEach(line => {
if (line) res.write(`data: ${line}\n\n`);
});
});
}).listen(3000);前端几行代码就能看实时日志,无需任何第三方库,也不需要处理 WebSocket 的握手和心跳。
一句话总结
SSE 就是基于 HTTP 的服务器单向推送协议。格式简单(data: + \n\n),浏览器原生支持(EventSource),自带重连和断点续传。如果你只需要服务器往浏览器发消息,完全没必要上 WebSocket,SSE 才是那把顺手的螺丝刀。
发表评论: