一、Server-Sent Events的介绍
Server-Sent Events服务器发送事件,简称SSE。服务器主动给客户端发送消息,常见的有websocket、sse也是其中的一种。
SSE是HTML5规范的一部分,该规范主要由两部分组成,第一部分是服务端与客户端的通讯协议http协议,第二部分是浏览器可供JavaScript使用的EventSource对象。
严格来说http协议是无法做到服务器主动推送消息给客户端的,但是有一种变通的方法,就是服务器向客户端声明,接下来要推送的数据是流式数据。此刻浏览器就明白了这个数据并非是一次性的数据,而是一个数据流,会连续不断的发送过来。
SSE就是利用这种机制,使用流信息向浏览器发生信息,注意目前IE浏览器不支持。
二、SSE和WebSocket对比
SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息
- websocket是全双工通道,可以双向通信,sse是单向通道,只能服务器向浏览器推送信息。
- sse使用http协议,现在的服务器软件都支持,websocket是一个独立的协议
- sse属于轻量级,使用简单,websocket相对复杂
- sse内置断线重连和消息追踪功能,websocket需要自己实现
- sse支持自定义发送的消息类型
- sse一般只用于传送文本,二进制数据需要编码后传送,websocket默认支持二进制数据传送
三、SSE使用实例
1、服务器的实现
这里以nodejs服务器为例实现推送数据流,实际上还是http那一套,只需要把数据设置为流式数据,在通过PassThrough实例对象向客户端推送数据即可。
// 利用express框架搭建服务器
const express = require('express');
const app = express();
const router = express.Router();
const cors = require('cors')
const port = 3000;
// PassThrough流是Node.js中的一个流类型,属于双工流(duplex stream),
// 既可以读取数据,也可以写入数据。PassThrough流的主要特点是它不会对数据进行任何更改,
// 只是简单地将从可读流传递来的数据传输到可写流,通常用于需要将数据从一个流传递到另一个流的情况下,而无需对数据进行额外的处理
const { PassThrough } = require("stream")
// 解决跨域问题
app.use(cors())
router.get("/api/sendMsg", (req,res)=>{
// 设置流式数据
res.set({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
})
// 中断连接,浏览器一般默认3秒回进行重连
// 也可以通过retry设置浏览器重新发起连接的时间间隔
// 两种情况会导致浏览器重新发起连接:一种是时间间隔到期,二是由于网络错误等原因,导致连接出错
res.write("retry: 10000\n");
//
const stream = new PassThrough();
res.body = stream
stream.pipe(res);
// 这里用定时器模拟写入数据,向客户端传递数字1到10
let i=1;
let timer = setInterval(() => {
if(i>10){
// 结束的时候发送stop表示此次请求的服务端数据推送完毕
stream.write(`data: stop\n\n`);
clearInterval(timer);
// 结束流时调用end方法 确保所有的数据都被写入目标流,并且释放相关资源
stream.end();
}else{
// 写入数据格式:data: content\n\n
// data是客户端接收数据的键名,content是发送客户端的内容,`\n\n` 表示一条消息发送结尾。
// 写入数据是复杂数据类型的时候通过通过JSON.stringify方法转换为字符串再传递
stream.write(`data: ${i}\n\n`);
i++;
}
}, 500);
})
app.use(router)
app.listen(port ,()=>{
console.log(`服务器启动成功:http://localhost:${port}`);
})
2、客户端的实现
客户端使用HTML+JavaScript实现接口的请求和数据的接收
<body>
<button>请求接口</button>
<div class="result"></div>
<script>
const btn = document.querySelector('button')
const result = document.querySelector('.result')
btn.addEventListener('click', () => {
// 实例化EventSource对象,并传入请求接口地址 向服务器发起连接
const evtSource = new EventSource('http://localhost:3000/api/sendMsg')
// 监听message事件并接收数据
evtSource.onmessage = (e) => {
console.log("接收的数据:",e.data)
// 判断数据是否已结束,结束需要关闭连接
if(e.data == "stop"){
return evtSource.close()
}
// 保存并渲染数据
result.innerHTML += e.data
}
// 错误处理
evtSource.onerror = (e) => {
console.log("错误信息:",e)
}
})
</script>
</body>
3、效果演示
四、其他说明
1、EventSource 对象
SSE 的客户端 API 部署在EventSource对象上。下面的代码可以检测浏览器是否支持 SSE
if ('EventSource' in window) {
// ...
}
2、readyState属性
EventSource实例化对象有readyState属性,用于表示当前的连接状态,该属性只读,取值如下:
- 0:相当于常量EventSource.CONNECTING,表示连接还未建立,或者断线正在重连。
- 1:相当于常量EventSource.OPEN,表示连接已建立,可以接收数据。
- 2:相当于常量EventSource.CLOSED,表示连接已关闭,且不会重连。
3、事件
sse建立连接开始默认会触发以下三种事件:
- open:表示连接一旦建立,就会触发open事件。
- message:客户端收到服务器推送的数据,就会触发message事件,通过事件对象event.data接收数据。
- error:如果发生通信错误或连接中断,就会触发error事件
使用方法:
const source = new EventSource(url)
// 方式一:通过on事件名
source.on事件名 = function(event){
console.log(event.data)
}
// 方式二:通过addEventListener监听
source.addEventListener("事件名", (event)=>{
console.log(event.data)
})
4、自定义事件
默认情况下,服务器发送的数据,会自动触发浏览器EventSource实例的message实际,开发者还可以自定义sse事件,这种情况下发生回来的数据不会触发message事件,而是触发自定义事件
4-1、服务端自定义事件
以上面的nodejs案例为例,只需要在推送数据之前写入自定义事件名即可
const stream = new PassThrough();
stream.write(`event: msg\n`) // 自定义事件名 msg
stream.write(`data: ${i}\n\n`);
4-2、客户端接收
服务端设置了自定义事件后,客户端必须使用addEventListener来监听自定义事件才生效。
const evtSource = new EventSource('http://localhost:3000/api/sendMsg')
evtSource.addEventListener("msg", function(e){
//自定义事件必须通过addEventListener来监听并接收数据
console.log(e.data)
})
5、客户端向服务端发送数据
客户端向服务端发送数据直接按照正常的http协议get请求进行查询参数传递即可。
5-1、客户端
let msg = "你好"
const evtSource = new EventSource('http://localhost:3000/api/sendMsg?msg='+encodeURIComponent(msg))
5-2、服务端
router.get("/api/sendMsg", (req,res)=>{
// 接收前端传入数据 msg
const { msg } = req.query;
})
五、使用场景
SSE在大模型AI聊天助手中可以使用到该技术,以流式数据的方式展示内容,提高用户体验,效果如下图: