公司项目最近有一个创建”我的待办“的需求,需要接收服务端的数据来实时提醒用户有新消息需要查看,用户可以点击小铃铛上的红点跳转到”我的待办“页面进行消息的查看。我通过这个需求了解到了这种Web实时通信的方式。
什么是Server-Sent Events(SSE)?
要实现Web的实时通信,将服务端的数据发送到客户端,目前有几种实现方式:
- 长轮询或者短轮询(客户端拉取请求)
- Websocket(服务端推送)
- SSE(服务端推送)
SSE和Websocket都是由服务端推送消息,虽然Websocket具有更多的功能,如支持全双工通信,即客户端也可以向服务端推送消息,但Websocket使用起来很重,不够轻量,需要服务端的支持。在这个需求中只要客户端接收服务端的推送消息,就可以使用更为轻量简单的SSE。SSE是HTML5中的一个API,在js中可以new一个浏览器提供的EventSource对象来监听服务器推送的事件。在建立连接后服务器可以通过http响应的text/event-stream类发送消息,浏览器则可以通过监听实例对象的 onmessage、onopen 和 onerror 事件来处理这些消息,这个连接会持续到调用EvenSource.close()结束。
// url为接口地址,option,option默认设置为false,即不适用CORS
const evenSourse = new EventSource(url, { withCredentials: true/false })
eventSource.onmessage = function(event) { console.log('接收到的信息:', event.data); }
eventSource.onopen = () => {
console.log('open')
}
eventSource.onerror = () => {
console.log('error')
}
// 手动关闭连接
evenSource.close()
SSE实现Demo
const http = require("http");
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
// 设置响应头部
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Access-Control-Allow-Origin": "*",
});
// 发送初始数据
res.write("data: Hello\n\n");
// 每隔一秒发送一条数据
const intervalId = setInterval(() => {
res.write("data: " + (new Date()) + "\n\n");
}, 1000);
// 当客户端关闭连接时停止发送数据
req.on("close", () => {
clearInterval(intervalId);
res.end();
});
});
// 启动服务器并监听指定端口
server.listen(3000, () => {
console.log("SSE server is running on port 3000");
});
<!DOCTYPE html>
<html>
<head>
<title>SSE Example</title>
</head>
<body>
<h1>SSE Example</h1>
<button id="connectBtn">Connect</button>
<button id="disconnectBtn">Disconnect</button>
<div id="dataContainer"></div>
<script>
const connectBtn = document.getElementById("connectBtn");
const disconnectBtn = document.getElementById("disconnectBtn");
const dataContainer = document.getElementById("dataContainer");
let eventSource;
connectBtn.addEventListener("click", () => {
// 建立 SSE 连接
eventSource = new EventSource("http://localhost:3000");
// 监听事件流消息
eventSource.onmessage = (event) => {
try {
console.log(event.data)
} catch (error) {
console.error("Error parsing JSON:", error);
}
};
// 监听错误事件
eventSource.onerror = (error) => {
console.error("Error occurred:", error);
};
});
disconnectBtn.addEventListener("click", () => {
// 断开 SSE 连接
if (eventSource) {
eventSource.close();
dataContainer.innerHTML += "<p>Disconnected from server.</p>";
}
});
</script>
</body>
</html>
项目中实现SSE优化
在公司项目中根据实际需要对SSE的实现进行了优化,主要是一些关闭连接的操作。比如同时有大量用户或客户端尝试与服务器建立连接时,可能会导致连接数超过限制,在后端接口中增加了这样返回信息,如果返回连接数超过限制,前端需要手动关闭连接,另一种情况是当页面被关闭时也需要手动关闭连接。
eventSource.current.onmessage = (e: any) => {
if (e?.data === 'Too many connections') {
// 连接超过限制
eventSource.close()
}
}
window.onbeforeunload = () => {
eventSource.close()
}
SSE断线重连
SSE的浏览器端实现了断线重连功能,在建立 SSE 连接时,可以设置一个 retry 值,该值表示客户端在断开后等待多长时间后进行下一次连接尝试。在断开连接时,客户端会根据该值自动重试连接。(待更新)