持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
SSE(Server Send Event)是一个spring mvc提供的轻量的服务端推送消息的框架.
与之对应的是耳熟能详的webSocket框架,两者的区别是:
- sse是单工通信,只能服务端给客户端推送消息,而websocket是双工通信,客户端和服务端可以双向通信
- websocket是单独的ws协议,而sse是基于http协议
两者的应用场景并不一致,如果仅仅是需要服务端给客户端推送消息的情况下,并不需要使用websocket,比如下载进度,提示消息等不需要客户端向服务端发送消息的功能,可以采用sse.
当然ws有着更强大的功能,例如网页聊天的场景就必须要借助ws了,强大灵活的功能带来的就是配置及使用复杂.所以需要结合具体的业务分析使用哪种技术.
sse的原理是在http协议上发送流式数据以达到长连接的效果,类似视频在线观看,需要设置响应头为contentType=text/event-stream,告诉浏览器收到响应后不要结束此次请求,而是继续等待,以便于数据的传输.
来看下sse的简单应用:
1. sse简单使用
1.1 创建连接
public SseEmitter createConnect(String userId) {
// 0L永不超时 否则会在配置时间后断开连接,而客户端会尝试重连
SseEmitter sseEmitter = new SseEmitter(0L);
cache.put(userId, sseEmitter);
try {
sseEmitter.send(SseEmitter.event().id("cust").data("test" + userId));
} catch (IOException e) {
e.printStackTrace();
}
return sseEmitter;
}
返回一个SseEmitter对象,springmvc会在返回处理器里对这个对象进行处理,设置响应头contentType=text/event-stream
上面的例子里还创建完连接后,还发送了一条消息.
在测试中发现,如果不发送消息,有几率无法推送消息,还没搞清楚是啥问题..
1.2 销毁连接
SseEmitter sseEmitter = cache.get(userId);
if (sseEmitter != null) {
sseEmitter.complete();
cache.remove(userId);
}
将sseEmitter对象销毁即可.
1.3 发送消息
public void sendMsgToClient(String userId, String id, Object data) {
if (StringUtils.isEmpty(userId)) {
cache.forEach((item, sseEmitter) -> {
SseEmitter.SseEventBuilder eventBuilder = SseEmitter.event().id(id).data(data);
sendMsg(sseEmitter, eventBuilder);
});
} else {
SseEmitter sseEmitter = cache.get(userId);
if (sseEmitter == null) {
throw new RuntimeException("长连接为空");
}
SseEmitter.SseEventBuilder eventBuilder = SseEmitter.event().id(id).data(data);
sendMsg(sseEmitter, eventBuilder);
}
}
private void sendMsg(SseEmitter sseEmitter, SseEmitter.SseEventBuilder eventBuilder) {
try {
sseEmitter.send(eventBuilder);
} catch (IOException e) {
e.printStackTrace();
log.error("推送消息客户端失败");
}
}
核心代码就是sseEmitter.send()方法.
这里使用的sseEventBuilder对象,可以设置发送数据的type(方便客户端区分不同类型的数据)
2. 网关gateway+消息队列RabbitMq实现群发消息
这个其实是具体的业务场景了
我遇到的业务经常需要服务器给所有的客户端推送消息,而这种场景有着如下问题:
- 服务端是多节点,客户端的sse连接可能在某一个节点上.
这个时候就需要如下操作:
- 将要推送的消息推送到消息队列RabbitMq(广播模式)
- 服务端每个节点均启动一个消费者,消费消息队列的这条消息
- 消费消息时判断需要推送消息的客户端sse连接是不是在本节点,如果是,则调用sseEmitter对象的send方法推送消息
加入网关是为了模拟真实情况,实际业务里一般都是客户端通过网关连接到服务端的.
具体代码不在这里复制粘贴,详细代码均上传github: github.com/zhangyao-zy…