做项目的时候遇到了顺便就记一下相关的内容。
SSE
Server-Sent Events(SSE)技术,它是一种用于实现服务器向客户端实时推送数据的Web技术。SSE基于HTTP协议,允许服务器将数据以事件流(Event Stream)的形式发送给客户端。客户端通过建立持久的HTTP连接,并监听事件流,可以实时接收服务器推送的数据。
SSE 和 Websocket
SSE 常常被用在与 Websocket 比较,但是他们的底层和使用有很大的区别,具体如下:
- SSE是服务器向客户端的单向通信,也就是连接完成后,只有后端可以向前端推送数据,前端被动的接收数据。而WebSocket是双向通信,后端和前端之间可以进行实时的双向数据交换
- SSE 使用基于HTTP 协议的长连接,通过的 HTTP 请求和响应来建立连接;而WebSocket 使用基于 TCP 的协议,通过建立 WebSocket 连接来实现双向通信。
- SSE 长用在实时场景中,比如事实更新价格和数据,实时获取服务器运行状态、日志等信息等。而 WebSocket 多用在双向通信的场景,比如多人聊天、协作编辑等
SSE 的原理
SSE在服务器和客户端之间打开一个单向通道,服务器响应的不是一次性的数据包,而是 text/event-stream
类型的数据流信息,在有数据变更时从服务器流式传输到客户端。
SSE 的后端代码(Nest.js)
因为项目是 nest.js 的所以这里就用 nest.js 来写一个例子,框架搭建过程就省略了,用的是基础的脚手架,以下的 SSE 的核心代码:
- 首先 nestjs 已经给我们封装好了 Sse 请求,我们只需要使用
@Sse
装饰器填写对应的路由就可以使用它了
import { Controller, Get, Header, Sse } from '@nestjs/common';
import { AppService } from './app.service';
import { Observable, interval, map } from 'rxjs';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Sse('/sse')
sse(): Observable<any> {
return interval(1000).pipe(map((_) => ({ data:'hello world' } )));
}
}
- 当然你这样写也是可以的,因为SSE 请求本质也是 HTTP GET 请求,只不过 Content-Type 变成了 text/event-stream。
@Get('/sse')
@Sse()
@Header('Content-type', 'text/event-stream')
sse(): Observable<any> {
return interval(1000).pipe(map((_) => ({ data: 'hello world2' })));
}
- 这里我们发送的数据用到了 RxJS 库的可观察的对象(Observable) ,它采用了观察者模式设计,主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。可观察对象是惰性的,只有被订阅后才会执行。其中
Interval
函数的作用是每隔一段时间发出一个数值,你可以可以使用 RxJS 库的其他函数来实现内容的推送,只需要返回的内容是一个 Observable 对象即可实现 SSE 接口功能。 - 关于这部分的详细内容可以自行查阅 RxJS 库
// 间隔一秒发送数据
return interval(1000).pipe(map((_) => ({ data: 'hello world' })));
// 订阅后推送
const ob$ = new Observable((subscriber) => {
subscriber.next('hello')
subscriber.next('world')
subscriber.complete()
})
return ob$
SSE 的前端代码(React.js)
前端采用的是 React 来编写,我们需要通过 EventSource
函数来实现,它的作用是打通与一个 SSE 接口的连接,之后就可以一直接收其数据了:
const eventSource = new EventSource("http://localhost:3000/sse");
function Test() {
const [cnt, useCnt] = useState("");
eventSource.onmessage = ({ data }) => {
useCnt(cnt + data);
};
return (
<div>
{cnt}
</div>
);
}
其中 message
事件是收到信息时触发的,可以通过以下两种方式来触发
eventSource.addEventListener("message", (event) => {});
eventSource.onmessage = (event) => {};
其中 open
事件是连接建立时触发的
eventSource.addEventListener("open", (event) => {});
eventSource.onopen = (event) => {};
其中 error
事件是发生异常时触发的
eventSource.addEventListener("error", (event) => {});
eventSource.onerror = (event) => {};
当我们离开页面不再需要订阅这个接口时,我们可以通过 close
方法来关闭这个连接
eventSource.close();
一般情况下我们在场景中需要做的是在进入页面时连接 SSE,退出页面时关闭连接,所以我们使用 useEffect 来解决这个问题:
let eventSource: EventSource | null = null;
useEffect(() => {
eventSource = new EventSource("http://localhost:3000/sse");
eventSource.onmessage = ({ data }) => {
console.log(data)
};
return (() => {
eventSource && eventSource.close();
})
},[])
SSE 存在的问题
- 由于SSE依赖于HTTP长连接,如果连接数量过多,可能会导致服务器资源不足。
- 虽然大多数现代浏览器都支持SSE,但仍然有一些旧版本的浏览器不支持。在使用SSE时,要确保您的目标客户端支持SSE,或者提供备用的实时数据推送机制。
- SSE无法发送二进制数据,只能发送 UTF-8 编码的文本。如果应用程序需要发送二进制数据,就需要使用 Websocket。所以SSE一般多用于推送数据信息给前端,也不用在下载传输文件等内容上。