[Node.js] Server-Sent Events
远程通信方式
通信方式:
- Stdio: 推荐,高效、简洁、本地
- Streamable HTTP: 远程
前置知识
SSE 全称 Server-Sent Events,中文是“服务器发送事件”。是一种基于 HTTP 的单向通信协议,由浏览器发起连接,服务器可以持续不断地向客户端推送数据。
你可以把它想象成:“浏览器打开一个通道,然后服务器不断地往里面发消息。”
SSE 特点
- 协议:基于 HTTP(长连接)
- 方向:单向:服务器 -> 客户端
- 格式:文本流,内容类型为
text/event-stream
- 浏览器支持:所有现代浏览器都支持(IE 除外)
- 应用场景:单方面需要推送的时候。实时通知、消息流、状态更新、股票/天气数据等
消息格式
SSE 协议规定,服务器以 text/event-stream
格式不断推送消息,每条消息格式如下:
event: 事件名 # 可选,默认是 message 事件
id: 唯一ID # 可选
retry: 3000 # 客户端断线重连间隔,单位毫秒,可选
data: 内容 # 必需,可以多行
每条消息用空行 \n\n
作为结尾。
事件类型
如果服务器发送的数据中没有指定事件类型,浏览器端会将其作为默认事件类型 message
来处理:
data: 这是默认消息(data 代表要发送的消息)
客户端监听方式:
eventSource.addEventListener("message", (e) => {
console.log("默认事件:", e.data);
});
可以自定义事件名:使用 event:
字段
event: update(事件名)
data: 新的更新内容
客户端监听方式:
eventSource.addEventListener("update", (e) => {
console.log("收到 update 事件:", e.data);
});
课堂练习
SSE 服务器推送信息示例
import express from "express";
import { watch } from "chokidar";
import { join } from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const clients = new Set(); // 存储所有的客户端连接
app.use(express.static(join(__dirname, "public")));
app.get("/mcp", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// 假设现在客户端连接过来,那么这里就给客户端推送一个消息
res.write("event:connected\n"); // 事件名
res.write("data:你已经连接上SSE服务器\n\n"); // 推送的数据
clients.add(res);
// 在客户端断开连接的时候,会触发 close 事件
req.on("close", () => {
clients.delete(res);
});
});
// 监听目录
const watcher = watch(join(__dirname, "watched"), {
persistent: true,
ignoreInitial: true,
});
// 需要在目录发生变化的时候,通知所有的客户端
watcher.on("all", (event, path) => {
// 将监听到的变化的信息,通知所有的客户端
// 1. 组装要发送给客户端的信息
const payload = JSON.stringify({
event, // 当前资源发生变化 1. 新增 add 2. 删除 unlink 3. 修改 change
path, // 文件的路径
time: new Date().toLocaleString(),
});
// 2. 通知所有的客户端
for (const client of clients) {
client.write(`event: resource_changed\n`);
client.write(`data: ${payload}\n\n`);
}
console.log(`【发生了变更】${event} -> ${path}`);
});
app.listen(3000, () => {
console.log(`服务器已启动, 监听3000端口`);
});