我想通过这篇文章,以最简单的方式给大家讲解 SSE(event-stream)通信。
前后端均使用 js 来编写简单代码
后端代码:
需要安装 express:
shell
pnpm i express
index.js:
javascript
const express = require("express");
const app = express();
const PORT = 3000;
// 处理 SSE 连接
app.get("/events", (req, res) => {
// 设置必要的 HTTP 头
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// 允许跨域(如果需要)
res.setHeader("Access-Control-Allow-Origin", "*");
// 发送初始连接确认
res.write("retry: 10000\n\n");
// 定时发送消息(示例)
const sendEvent = (data, id) => {
res.write(`id: ${id}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
let messageId = 0;
const intervalId = setInterval(() => {
const data = {
message: `这是第 ${messageId} 条消息`,
timestamp: new Date(),
};
sendEvent("hello", messageId++);
}, 3000); // 每3秒发送一次
// 监听客户端关闭连接
req.on("close", () => {
clearInterval(intervalId);
res.end();
});
});
app.listen(PORT, () => {
console.log(`服务器正在运行在 http://localhost:${PORT}`);
});
为了方便测试,允许所有域名访问res.setHeader("Access-Control-Allow-Origin", "*")
代码很简单:
- 首先发送了
retry: 10000\n\n
,表示意外断开了,重新连接的延迟时间。这里是 10s。如果不发送retry
,chrome 会默认 5s - 每隔三秒向浏览器发送消息,虽然调用了两次
write
,但也是发送一条消息。 - 当客户端主动关闭连接后,会清除定时器
前端代码:
index.html:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>SSE 示例</title>
</head>
<body>
<h1>服务器发送的消息:</h1>
<ul id="messages"></ul>
<script>
const eventSource = new EventSource("http://localhost:3000/events");
eventSource.onmessage = function (event) {
console.log("event", event);
const data = JSON.parse(event.data);
const li = document.createElement("li");
li.textContent = `[${new Date(
data.timestamp
).toLocaleTimeString()}] ${data.message}`;
document.getElementById("messages").appendChild(li);
};
eventSource.onerror = function (err) {
console.error("EventSource 失败:", err);
eventSource.close();
};
</script>
</body>
</html>
启动前后端:
后端:
shell
node index.js
前端:
shell
http-server index.html
效果:
前端每收到一条消息,就会在页面上输出。同时在 network 的控制面板,也可以看到 eventStream 的消息流。
拓展:
测试完毕。上面就是SSE通信最基础的用法。
下面深入讲讲 SSE通信 的用法:
一
event-stream 的每条消息都是以\n\n
作为分割符的
上面后端代码中,第二次调用 write 时,消息末尾是\n\n
, 所以连同前一个 write的内容,视为一条消息。
二
消息格式:key: '...'\n
。这样的格式视为一个键值对
。
js
res.write('key: content');
其中content
,只允许是字符串。如果想向浏览器发送 json,务必先转成字符串。
下面着重讲讲 key
目前允许的 key
只有三种:id
,event
,data
,这三个 key 对应着下面event-stream中的 Id
,Type
,Data
在一条消息中,其中只有 data
不能缺失。其他的两个 key
都可以缺失。
id 在网络重连的时候很有用,如果缺失了 id
,不影响浏览器接收消息。
event
指定了消息的 Type
,如果 event
缺失,那么 Type
默认是 message
,如果指定了其他值,比如man
,效果就是下面这样:
javascript
res.write(`event: man\n`);
这个时候,前端接收消息就不能使用:
javascript
eventSource.onmessage = function (event) {
const data = JSON.parse(event.data);
...
};
而是要:
javascript
eventSource.addEventListener("man", (event) => {
const message = JSON.parse(event.data).message;
});
没错!Type 可以用来标识消息类型,就好像触发了不同的事件。
三
如果我想将 key
设置为 name
,会发生什么?答案是什么也不会发生,SSE 协议会忽略除上面三种之外的所有自定义 key
!!
字段类型 | 客户端是否处理 | 解决方案 |
---|---|---|
<font style="color:rgba(0, 0, 0, 0.86);">data</font> |
✅ 解析为 <font style="color:rgba(0, 0, 0, 0.86);">event.data</font> |
直接使用 <font style="color:rgba(0, 0, 0, 0.86);">data</font> |
<font style="color:rgba(0, 0, 0, 0.86);">event</font> |
✅ 触发特定监听事件 | 定义 <font style="color:rgba(0, 0, 0, 0.86);">event</font> + 监听对应事件 |
<font style="color:rgba(0, 0, 0, 0.86);">id</font> /<font style="color:rgba(0, 0, 0, 0.86);">retry</font> |
✅ 用于连接管理 | 按需使用 |
自定义字段 | ❌ 完全忽略 |
总结
这篇文章讲了 SSE
通信的简单用法,并且拓展讲了 SSE
中 key
使用时,要注意的点。大家可以在本地多尝试,加深印象