在 Web 应用和互动网站中,实时性是非常重要的因素,然而在以往的时代,实现实时性几乎只能靠轮询。轮询带来的带宽浪费和延迟时间,往往让实时性难以达成。现在,Server-sent Events (SSE) 呈现出成熟的方案,为开发人员提供了一种更加可靠并且更加有效的实时通信方式。
SSE 技术是 HTML5 的一部分,它允许 Web 浏览器与服务器之间实现单向持久连接。在这种连接下,服务器可以向浏览器发送数据,而浏览器只能接收数据。SSE 正式成为标准后,SSE 的兼容性得到较好的支持(IE 除外),现在我们可以在 Nodejs 中很容易地实现 SSE 和 SSE 事件的服务器发送。
SSE 的基本操作
SSE 事件格式
SSE 事件,由一个以冒号开头的行开始,随后是一行或者多行数据内容。它是具有以下格式的数据流:
: 这是注释行,也可以省略 event: 事件名称 data: 数据内容 id: 事件标识符 retry: 重试时间间隔
其中,冒号开头的行表示注释行,SSE 的注释行可以有任意数量,并且会被浏览器忽略。它们主要被用来保持连接的活跃,并且可以用来防止连接超时。
在我们的 SSE 事件中,首先可以选择一个时间实名,比如 message 、heartbeat 等等。然后,在 data 行中放入任意要发送的数据,这些数据都将作为文本发送给浏览器。需要注意,所有数据行都必须以换行符结尾。
如果你需要为你的 SSE 注册一个事件标识符 ID,你可以在事件数据流中添加一个 id 行。事件标识符可以让浏览器支持在断开连接后恢复连接时返回失联事件的最后一个表示状态,从而反复恢复历史状态。
最后还有一个 retry 行,这个可以设置服务器发送事件的时间间隔,如果没有设置,它将开始执行默认的重新连接机制,并且使用浏览器对常规 EventSource 连接设置的时间间隔。
一次 SSE 事件的示例
下面是在 SSE 事件数据流中示范了一个只包含最小信息的示例:
: 这是注释行 event: message data: This is a message id: 1 retry: 1000
创建 SSE 实例
在 Nodejs 中引入了 http 模块,可以很容易地创建一个 SSE 协议。以下是创建 SSE 服务的代码:
// www.javascriptcn.com code example
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
if (req.url === '/sse') {
res.writeHead(200, {
'Content-Type': 'text/event-stream', // 设置Content-Type
'Cache-Control': 'no-cache', // 禁用缓存
'Connection': 'keep-alive', // 保持连接
});
res.write('\n');
setInterval(() => {
res.write(`data: ${new Date()} \n\n`); // 发送数据
}, 1000);
} else {
res.end(fs.readFileSync(__dirname + '/index.html')); // 发送HTML页面
}
});
server.listen(3000, 'localhost', () => {
console.log('服务启动成功,访问地址为:http://localhost:3000');
});在上述的代码中,Content-Type 设置为 text/event-stream 定义了返回对应的格式,并且 Cache-Control 一般情况下都为 no-cache,这是告诉客户端不需要缓存数据,因为 SSE 已经向客户端发送了缓存列。而 Connection: keep-alive 则表示连接是需要维护的,否则可能会被浏览器中断。
setInterval() 主要用于向客户端发射数据,从而实现 SSE 服务。 setInterval() 的第一个参数是执行要发射的代码,第二个参数则是想要重复多久(以毫秒为单位)的代码。
对 SSE 连接事件的处理
通过开发者我们希望在有新客户端连接时会有一次回调通知,同时当我们关闭客户端时也需要关闭对应的连接,因此我们需要监听 req 和 res 对象的 close 事件和 end 事件。
完整的 SSE 服务代码如下:
// www.javascriptcn.com code example
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
if (req.url === '/sse') {
// 配置请求头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
res.write('\n');
// 客户端连接成功回调
req.on('open', function () {
console.log('[open] open')
})
// 客户端关闭连接回调
req.on('close', function () {
console.log('[close] close')
})
// 模拟发送 SSE 数据
const timerId = setInterval(() => {
res.write(`data: ${new Date()}\n\n`);
}, 1000);
// 给 SSE 连接发送新的响应头
res.on('close', function () {
console.log('[close] 收到了 SSE 客户端关闭请求,开始清理资源!')
clearInterval(timerId);
res.end();
})
} else {
res.end(fs.readFileSync(__dirname + '/index.html'));
}
});
server.listen(3000, 'localhost', () => {
console.log('服务启动成功,访问地址为:http://localhost:3000');
});实践指导
SSE 已经是浏览器标准实现,建议开发人员可以优先考虑使用 SSE,在提高前端实时性的同时,减少浏览器资源无用开销。
在 Nodejs 中构建 SSE 服务时,需要我们注意定制需要的 http 请求头及连接状态的监听,同时需要保证服务器能够长时间保持连接以确保 SSE 分发的可靠性。
在其它场景中,我们需要自己实现 SSE 协议或者使用一些开源库来提供对应的接口。以下是三个比较受欢迎的开源库:
- EventSource
- Server-sent-events
- SSE.js
可以通过阅读它们的开源代码,可以更好的理解 SSE 实现的细节。
小结
SSE 让开发者可以创建可靠、可伸缩并且低延迟的应用,其原理通过管理连接并降低请求负担。现在随着网络浏览器的普及,更高级别的 SSE 框架将变得更加容易实现。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d6bb3aa941bf7134c96038