介绍
最近刚好项目(生成式AI场景)中使用到了 SSE,就来梳理下SSE相关知识
我们知道,HHTP协议无法做到服务器主动推送消息,但是可以通过服务器向客户端声明表示接下来要发送的是流信息
客户端不会关闭连接,会一直等着服务器发过来的新数据,视频播放即是如此。本质上就是以流信息的方式完成一次用时很长的下载
SSE(Server Send Event)实时通信的服务器推送机制 (服务器向客户端实时推送数据)GitHub源码
即客户端从服务端订阅一条“流”,之后服务端可以发送消息给客户端直到服务端或者客户端关闭该“流”,所以 EventSource
也叫作 SSE(server-sent-event)
。
EventSource
是HTML5中的一项API,用于在客户端和服务器之间建立持久的、单向的通信连接。它基于HTTP协议,通过服务器推送的方式向客户端发送实时事件通知。客户端通过添加事件侦听器来捕获事件并执行相应的操作。
主要特点
- 简单易用:基于文本的数据格式,使得数据的发送和解析都相对简单
- 单向通信:支持服务器向客户端的单向通信,服务器可以主动推送数据给客户端
- 实时性:建立长时间连接,服务器可实时将数据推送给客户端,无需客户端频繁发起请求
兼容性
除了IE和低版本主流浏览器,市面上大多数浏览器都支持SSE
对比Websocket
SSE和WebSocket都是建立浏览器和服务器之间的通信渠道,然后服务器向浏览器推送消息
SSE | WebSockets | |
---|---|---|
协议 | 基于HTTP,使用标准HTTP连接 | 基于TCP协议 |
通信方式 | 单向通信(stream数据本质上就是下载) | 全双工通信 |
数据格式 | 文本(UTF-8编码) | 文本或二进制 |
重连机制 | 自动重连 | 手动实现重连机制 |
实时性 | 高(适合频繁更新的场景) | 非常高(适合高度交互的实时应用) |
浏览器支持 | 良好(大多数现代浏览器支持) | 非常好(几乎所有现代浏览器支持) |
适用场景 | 实时通知、新闻feed、股票价格等需要从服务器推送到客户端的场景 | 在线游戏、聊天应用、实时交互应用 |
复杂性 | 较低,易于实现和维护 | 较高,需要处理连接的建立、维护和断开 |
兼容性和可用性 | 更容易通过各种中间件和防火墙 | 可能需要配置服务器和网络设备以支持WebSocket |
服务器负载 | 适合较低频率的数据更新 | 适合高频率消息和高度交互的场景 |
对比HTTP的长连接PUSH
原理差不多,不过SSE已经写入HTML5标准,浏览器原生支持
长连接Push和SSE都可以实现服务器向客户端的实时推送,但SSE在实现上更简单,而且更适合于需要频繁发送数据的应用。
@microsoft/fetch-event-source
EventSource兼容性好,有很多优点。但也有无法解决的问题
- EventSource发出的请求默认GET请求,大部分浏览器GET请求有URL长度限制
- 无法传入自定义请求头
- 无法控制重试策略
微软有一个封装好的sse库较好解决了这2个问题:@microsoft/fetch-event-source
"@microsoft/fetch-event-source": "^2.0.1",
为了解决以上问题我们基于EventSource进行封装,可以实现如下功能
- 自定义请求头等其他信息
- 发送POST请求
- 控制重试策略。增加超时报错
- 对于收到消息的监听做封装处理
- 主动关闭连接
设计
客户端和服务端交互流程图如下所示:
修改request header:
Content-Type: text/event-stream
查看掘金侧边栏豆包问答,它回答一个问题不是一次性给我们全部,而是一部分一部分加载回答,这也是通过SSE实现通信
SSE类
我们基于@microsoft/fetch-event-source库封装SSE类:
var eventSourceFetcher = require('@microsoft/fetch-event-source');
class EventStream {
constructor(endpoint: string) {
this.endpoint = endpoint;
}
retrieveData(fetchParams = {}, callback) {
if (!this.endpoint) {
return;
}
const defaultRequestData = {
request_stream: 1
};
const requestData = Object.assign({}, defaultRequestData, fetchParams);
eventSourceFetcher(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
openWhenHidden: true,
body: JSON.stringify(requestData),
async onopen(serverResponse) {
if (serverResponse.ok && serverResponse.status === 200 && serverResponse.headers.get('content-type')?.includes('text/event-stream')) {
console.log("Successfully connected");
} else {
console.log('Request error');
}
},
async onmessage(messageEvent) {
try {
const parsedData = JSON.parse(messageEvent.data);
console.log('Received data', parsedData);
callback(parsedData)
if (parsedData.isEnd) {
return;
}
} catch (parsingError) {
console.log('Error parsing data', parsingError)
}
},
onerror(error) {
console.log('Error', error);
},
onclose() {
console.log('Connection closed');
}
});
}
}
exports.EventStream = EventStream;
使用
将携带参数和请求回调一起传入:
function dataCallback (receivedData) {
console.log('Received stream data', receivedData);
};
const eventStreamInstance = new EventStream('/api/info/sse');
eventStreamInstance.retrieveData(params, dataCallback);
风险
- 如果不使用HTTP/2会受到最大打开连接数的限制,限制最大连接数为6(HTTP/2默认值是100)
- eventSource 默认只能支持get请求:使用@microsoft/fetch-event-source 将sse转为post
- 首次请求事件间距过长 或长时间请求不到超时
stackoverflow.com/questions/1…
- MS(Microsoft)浏览器至今不支持SSE?
有很多sse polyfills可以使用
Internet Explorer不支持Server-Sent Events (SSE);但是Edge(基于Chromium)支持SS的
- SSE只能接收文本数据吗?
只支持utf8编码,binary数据可以先base64