[Node.js] Server-Sent Events

远程通信方式

通信方式:

  1. Stdio: 推荐,高效、简洁、本地
  2. Streamable HTTP: 远程

前置知识

SSE 全称 Server-Sent Events,中文是“服务器发送事件”。是一种基于 HTTP 的单向通信协议,由浏览器发起连接,服务器可以持续不断地向客户端推送数据

你可以把它想象成:“浏览器打开一个通道,然后服务器不断地往里面发消息。”

SSE 特点

  1. 协议:基于 HTTP(长连接)
  2. 方向:单向:服务器 -> 客户端
  3. 格式:文本流,内容类型为 text/event-stream
  4. 浏览器支持:所有现代浏览器都支持(IE 除外)
  5. 应用场景:单方面需要推送的时候。实时通知、消息流、状态更新、股票/天气数据等

消息格式

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端口`);
});
posted @ 2025-10-06 02:53  Zhentiw  阅读(21)  评论(0)    收藏  举报