概述
常见从服务端获取数据的方式有很多
- 轮询(长/短方式)
就是客户端按照一定的频率定期向服务器获取数据,兼容性很好。 - Comet
主要获取数据的方式是长轮询和 iframe 流,不管是哪种都是通过建立长链接来使得服务器发送消息给客户端,前者使用轮询(每次轮询时候会一直等待直到服务端返回数据才关闭连接),后者使用页面的 iframe 标签的 src 属性向服务端建立长连接来获得数据。不过后期出现的 WebSocket 有取代 Comet 的趋势。 - WebSocket
客户端和服务端通过”升级版“的 HTTP 协议进行全双工通信,服务端可以发送消息给客户端,客户端也可以发送消息给服务端,需要考虑兼容性。 - SSE
是 Server sent event 的简写,服务端推送消息给客户端,但是客户端不能主动发送消息给服务端。并且基于 HTTP 协议,header部分有变化,需要考虑兼容性。
SSE 基本内容
概述
-
SSE 通过 HTTP 协议使服务器推送数据给客户端。
-
在只要服务端而不用客户端发送数据的场景下可以考虑用。
- 客户端一次请求但是返回数据量很大/慢,服务器分批次返回
- 客户端请求服务器后需要回调通知
-
默认支持客户端自动断线重连。
-
HTTP 的请求/响应头会有所不同,主要是这几个字段:
request header
Accept:text/event-stream
response header
Connection:keep-alive Content-Type:text/event-stream
意思是请求的时候告诉服务器这是”事件流“,服务端响应我的连接是”长连接“,并且返回的数据是”文本的事件流“数据。
服务端可能会多次返回数据,类型如下
data:{"type":"COMMENT","keyList":["过去","gg"],"searchResponseItem":null} data:{"type":"CATEGORY","keyList":["过去","gg"],"searchResponseItem":null} data:{"type":"TAG","keyList":["过去","gg"],"searchResponseItem":null} data:{"type":"FRIEND_LINK","keyList":["过去","gg"],"searchResponseItem":null} data:{"type":"ARTICLE","keyList":["过去","gg"],"searchResponseItem":[]} event:finish data:stream finish
在一次连接中服务器总共发送了 6 次数据,每一次一行,其中最后 2 行是一次发送,表达的是发送”finish“事件并且本次事件的数据是”stream finish“。
具体数据格式怎么辨别可以查看文档
-
需要有一定兼容性的考量;当不使用 HTTP2 的时候,浏览器会对同个域下的 SSE 连接数进行限制为 6,如果是 HTTP2 则默认同个域下 100 个连接数,并且可以通过握手协商设置。
SSE 代码 Demo
客户端(以js为例)
const streamSearch = (): void => {
const source = new EventSource("https://www.ahao.homes");
source.onopen = () => {
dev(() => console.log('start stream search...'));
// 设置客户端超时事件
const timeoutId = setTimeout(() => {
// 如果超时了服务器还没返回则客户端关闭连接
if (source.CLOSED !== source.readyState) {
dev(() => console.log('stream search timeout...'));
source.close();
}
clearTimeout(timeoutId);
}, SEARCH_TIME_OUT);
};
source.onmessage = (e) => {
dev(() => console.log(e.data));
};
// 客户端自定义的"finish"事件
source.addEventListener('finish', () => {
dev(() => console.log('finish stream search...'));
source.close();
});
source.onerror = () => {
dev(() => console.log('stream search error...'));
source.close();
};
};
其中
- new EventSource
new EventSource("https://www.ahao.homes");
表示向"https://www.ahao.homes"发送HTTP的GET请求来建立连接
这里似乎不能 POST 请求,并且不支持在 body 内部传入参数,只能通过将参数放到 url 内部方式传参,比如
new EventSource("https://www.ahao.homes/12345");
- readyState
0 — connecting
1 — open
2 — closed
- EventSource 的事件 API
方法名 | 说明 |
---|---|
onopen | 当建立连接的时候 |
onmessage | 当服务器发送的消息到达客户端的时候 |
onerror | 当连接发生异常的时候 |
- EventSource 的自定义事件 API
自定义"finish"事件表示服务端发送的关闭连接事件source.addEventListener('finish', () => { dev(() => console.log('finish stream search...')); source.close(); });
服务端(以java的SpringBoot为例)
@GetMapping("/api/streamSearch/{key}")
public SseEmitter streamSearch(@PathVariable String key) {
// 创建SseEmitter类,并设置服务端的超时时间
SseEmitter sseEmitter = new SseEmitter(SEARCH_TIME_OUT_MS);
// 要在不同于主线程的线程里,不然似乎不可以
new Thread(() => {
// 服务器第一次发送数据
sseEmitter.send(SseEmitter.event().data("data"));
// 服务器第二次发送数据
sseEmitter.send(SseEmitter.event().data("data"));
...
// 服务器第n次发送数据
sseEmitter.send(SseEmitter.event().data("data"));
// 服务器发送"finish"事件并且数据是"stream finish"
sseEmitter.send(SseEmitter.event().name("finish").data("stream finish"));
}).start();
return sseEmitter;
}
其中
主要是运用SseEmitter这个类实现SSE。
这个类还有很多别的方法比如
- onCompletion()
- onError()
- onTimeout()
😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁