应用场景
在目前项目中,经常有好多交互信息需要事实的更新, 比如说我们的echarts数据大屏就需要这样 实时数据的时候。我们可以采用websocket,但是呢我感觉连接成本会增加,而基于sse的话
Server Send Event(SSE)概念
Server Send Event (SSE)是HTML5的API,用于在服务器和客户端之间实时推送数据流。它与WebSocket不同的是,SSE是一个单工通信,是服务端向客户端定向的推送消息。
Server Send Event协议
SSE协议本质上就是一个Http的get请求,当然也是支持Https,服务端在接到该请求后,返回状态。同时请求头设置也变为流形式。
txt
Content-Type: text/event-stream,
Cache-Control: no-cache,
Connection: keep-alive
客户端实现
客户端通过EventSource对象与服务器的一个http get请求建立长连接。
new EventSource()建立与服务端的连接。
onmessage()用来监听服务端发来的消息。
onerror()用来监听连接的错误。
客户端代码如下。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>Server Send Event测试</h2>
<ul id="ul"></ul>
<script>
const source = new EventSource("http://localhost:3000/api/sse");
// sse.addEventListener("test", (event) => {
// console.log(event);
// console.log(event.data);
// });
// sse.onerror = function (error) {
// console.error("Error occurred:", error);
// };
source.onopen = () => {
console.log("连接成功");
};
source.onmessage = (res) => {
console.log("获得的数据是:" + res.data);
var ulDoom = document.getElementById("ul");
var child = document.createElement("li");
child.innerHTML = res.data;
ulDoom.append(child);
};
source.onerror = (err) => {
console.log(err);
};
</script>
</body>
</html>
服务端实现
PassThrough将字节转成流形式。
router.get('/sse', async (ctx, next),创建一个get请求(也就是路由,用来响应)。
Content-Type': 'text/event-stream 这里必须将请求设置成流的形式 ctx.body = stream;将消息以流的形式返回给客户端
js
const Koa = require("koa");
const app = new Koa();
const { PassThrough } = require("stream");
const router = require("koa-router")({
prefix: "/api",
}); // 注意这里最后是函数调用
const cors = require("koa-cors");
router.get("/hello", async (ctx, next) => {
ctx.body = { message: "Hello, World!" }; // 返回一个JSON对象
});
const sendMessage = async (stream) => {
const data = [
"hello,jerry",
"have not see you for a long time",
"how are you",
"what are you doing now ",
"i am missing you",
];
// 循环上面数组: 推送数据、休眠 2 秒
for (const value of data) {
stream.write(`data: ${JSON.stringify(value)}\n\n`); // 写入数据(推送数据)
await new Promise((resolve) => setTimeout(resolve, 2000));
}
// 结束流
// stream.end();
};
// SSE 路由处理
router.get("/sse", async (ctx, next) => {
// 设置响应头
ctx.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
// 2. 创建流、并作为接口数据进行返回
const stream = new PassThrough();
ctx.body = stream;
ctx.status = 200;
// 3. 推送流数据
sendMessage(stream, ctx);
});
app.use(
cors({
origin: function (ctx) {
// 允许指定域名或者所有源('*')
return "*";
},
credentials: true, // 允许携带cookie
allowMethods: ["GET", "POST", "PUT", "DELETE"], // 允许的方法列表
allowHeaders: ["Content-Type", "Authorization"], // 允许的请求头列表
})
);
app.use(router.routes());
// 监听端口
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
运行代码的结果如下:
Server-Sent Events (SSE) 优点:
- 简单性 :基于HTTP/HTTPS协议,实现相对简单,只需设置特定的MIME类型(
text/event-stream
)并保持一个长连接。 - 兼容性:大多数现代浏览器支持SSE,不需要额外的客户端库。
- 单向推送:对于仅需要服务器到客户端的数据更新,比如股票报价、新闻更新等场景很适合。
- 自动重连:如果连接中断,SSE会尝试重新建立连接。
- 可发送多事件序列:服务器可以连续发送多个独立事件。
SSE 缺点:
- 单向通信:只能由服务器向客户端发送数据,不支持客户端主动向服务器发送消息。
- 网络不稳定:虽然具备重连机制,但在网络波动频繁的情况下,可能造成更新延迟或丢失。
- 流量控制:相较于WebSocket,缺乏更精细的流量控制功能。
WebSocket 优点:
- 双向通信:允许服务器与客户端之间进行全双工通信,实时性强。
- 低延迟:一旦建立连接后,数据传输速度快且延迟低,适用于实时聊天、游戏等应用。
- 持续连接:通过TCP持久连接,无需轮询,资源消耗较低。
- 灵活的消息格式:支持自定义消息结构,不受限于SSE规定的格式。
- 更好的性能:在大量并发和高频通信场景下,性能表现优于长轮询方案。
WebSocket 缺点:
- 复杂性:实现和配置通常比SSE复杂,需要客户端和服务端都支持WebSocket协议。
- 兼容性:尽管主流浏览器普遍支持WebSocket,但旧版或某些特殊环境下可能存在兼容问题。
- 开销:每条WebSocket连接都需要维持一个独立的TCP连接,对服务器资源需求较高。
选择使用SSE而不是WebSocket的原因通常在于以下几点:
- 如果只需要单向通信,而不需要客户端主动发送数据给服务器。
- 对于简单的数据推送场景,SSE实现更为简洁易用。
- 当项目中已有的基础设施更加适应基于HTTP的服务端推送。
然而,在要求双向实时通信、交互频繁或者对延迟要求较高的应用中,WebSocket通常是更合适的选择。