服务端进行数据推送除了WebSocket
之外,还可以使用Server-Send-Event
方案。
与 WebSocket
不同的是,服务器发送事件是单向的。数据消息只能从服务端到发送到客户端(如用户的浏览器)。这使其成为不需要从客户端往服务器发送消息的情况下的最佳选择。
Server-Sent-Events
(SSE
)是一种HTML5
API
,用于在服务器和客户端之间实时推送数据流。 SSE
可以用于实现实时通知、实时聊天、实时数据更新和实时监控等功能。
服务器发送事件API包含在EventSource
接口中
EventSource
接口是 web 内容与服务器发送事件通信的接口。
一个 EventSource
实例会对HTTP
服务器开启一个持久化的连接,以 text/event-stream
格式发送事件,此连接会一直保持开启直到通过调用EventSource.close()
关闭。
创建一个EventSource
对象,开启与服务器的连接并接收事件
const evtSource = new EventSource("ssedemourl")
如果生成事件的代码不同源,需要创建一个新的包含url和options参数的EventSource
对象
const evtSource = new EventSource("//api.example.com/ssedemourl", {
withCredentials: true,
})
事件流是一个简单的文本数据流,文本应该使用 UTF-8
格式的编码。事件流中的消息由一对换行符分开。以冒号开头的行为注释行,会被忽略。
每个字段由字段名表示,后面是冒号,然后是该字段值的文本数据。
字段有以下几个
event
用于标识事件类型的字符串。如果指定了这个字符串,浏览器会将具有指定事件名称的事件分派给相应的监听器;客户端应该使用 addEventListener()
来监听指定的事件。如果一个消息没有指定事件名称,那么可以调用onmessage
处理程序。
data
消息的数据字段。当 EventSource
接收到多个以 data:
开头的连续行时,会将它们连接起来,在它们之间插入一个换行符。末尾的换行符会被删除。
id
事件 ID,会成为当前 EventSource
对象的内部属性“最后一个事件 ID”的属性值。
retry
重新连接的时间。如果与服务器的连接丢失,浏览器将等待指定的时间,然后尝试重新连接。这必须是一个整数,以毫秒为单位指定重新连接的时间。如果指定了一个非整数值,该字段将被忽略。
所有其他的字段名都会被忽略。
示例:
纯数据messagee
data: some text
命名事件
event:usermessage
data: {"username": "bobby", "age": "22"}
混合事件,在一个事件流中同时使用命名事件和未命名事件
data: some text
event:usermessage
data: {"username": "bobby", "age": "22"}
open
事件EventSource
接口的onopen
属性是一个事件处理器,它在收到 open
事件时被调用,在那时,连接刚被打开。
evtSource.onopen = function () {
console.log("Connection to server opened.");
}
message
事件如果服务器发送的消息中没有定义event
字段,这些消息则都为message
事件,要接收从服务端发送过来的message
事件,需要为message
事件添加事件处理程序
前端部分代码:
evtSource.onmessage = (event) => {
console.log(event.data)
}
// onmessage的等价写法
evtSource.addEventListener('message', (event) => {
console.log(event.data)
});
服务端部分示例代码:
stream.write(`data: ${song[index]}\n\n`)
event
字段中的自定义事件如果服务器发送的消息中定义了event
字段,以event
中给定的名称来接收事件
前端部分代码:
evtSource.addEventListener('custom', (e) => {
console.log(.data)
})
服务端部分示例代码:
stream.write(`event: custom\ndata: ${song[index]}\n\n`) // 此处定义了event字段,名称为custom
当发送网络超时或其他问题时,会生成一个错误事件。在事件源连接未能打开时触发
evtSource.onerror = (err) => {
console.error("EventSource failed:", err);
}
EventSource
事件默认情况下,如果客户端和服务器之间的连接关闭,则SSE连接会重新启动,可以在前端的事件上调用close()
方法终止连接。
evtSource.close()
发送事件的服务端脚本需要使用text/event-stream
MIME类型响应类型。每个通知以文本块形式发送,并以一对换行符结尾。
Node.js中使用koa2演示SSE连接,服务端示例代码
const router = require('koa-router')()
const { PassThrough } = require('stream')
// 测试EventSource
router.get('/eventSource', async (ctx, next) => {
try {
const stream = new PassThrough()
ctx.set({ 'Content-Type':'text/event-stream' })
// 将歌词变成一个数组
let song = [
'我', '懒', '得', '写', '你', '谷', '搜', '到', '处', '皆', '只', '因', '你',
'太', '美', '浅', '唱', '动', '人', '说', '不', '出', '我', '试', '着', 'end',
]
let index = 0;
setInterval(() => {
const str = song[index];
if (str) {
// 方式1:格式是 data: xxx\n\n,事件要一对\n结束
// stream.write(`data: ${song[index]}\n\n`)
// 方式2,定义了event字段,自定义事件
stream.write(`event: custom\ndata: ${song[index]}\n\n`)
// 发送的数据为json字符串
// stream.write(`event: custom\ndata: {"name": "${song[index]}"}\n\n`)
} else {
stream.write('0')
}
index++
}, 500)
ctx.body = stream
} catch (error) {
error.status = error.status ? error.status : 500
ctx.throw(error.status, error)
}
})
前端使用的vue3
const getSSE = () => {
const source = new EventSource(`http://localhost:8081/api/eventSource`);
let str = ''
source.onopen = () => console.log("Connected");
source.onerror = console.error;
// 方式1:监听message事件
// source.onmessage = (e) => {
// if (e.data === 'end') {
// // 判定end,关闭连接
// source.close()
// }
// str += e.data
// console.log(str)
// }
// 方式2,监听自定义custom事件
source.addEventListener('custom', (e) => {
str += e.data
// str += JSON.parse(e.data).name // 接收json字符串
console.log(str)
})
}
getSSE()
效果演示
现在很火的ChatGPT在回答中的打字效果就是使用了SSE的方式。