💡如何使用SSE通信(Event-stream)

我想通过这篇文章,以最简单的方式给大家讲解 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", "*")

代码很简单:

  1. 首先发送了 retry: 10000\n\n,表示意外断开了,重新连接的延迟时间。这里是 10s。如果不发送retry,chrome 会默认 5s
  2. 每隔三秒向浏览器发送消息,虽然调用了两次 write,但也是发送一条消息。
  3. 当客户端主动关闭连接后,会清除定时器

前端代码:

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 使用时,要注意的点。大家可以在本地多尝试,加深印象

相关推荐
夏幻灵4 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
三天不学习5 小时前
Egg.js 4 突然爆火,原因是否归结于AI 原生落地需求爆发?
前端·javascript·全栈·egg.js
发光小北5 小时前
四路can转WIFI在现场应用中有什么问题?
网络协议
xcs194056 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
时间的拾荒人10 小时前
Qt 界面优化:绘图详解 —— 从基础图形到图片处理
开发语言·qt·面试
小宋102111 小时前
A2UI从零实战:Agent安全生成交互界面与事件回传
javascript·人工智能·安全·交互
沐言人生11 小时前
又一个神级考公脑库-22,375篇真题终于按考点整理了
后端·面试
大包子11 小时前
structuredClone vs lodash.cloneDeep:核心差异与选型指南
javascript·vue.js
for_ever_love__11 小时前
MySQL 高频面试题 30 问:索引、事务、锁、日志、主从与分库分表(MySQL 8.0 版)
java·数据库·spring boot·mysql·spring cloud·面试·总结
lerhxx12 小时前
AI 应用中的上下文管理 —— 从"上下文窗口"到"分层压缩"的工程实践
前端·javascript