spring boot整合SSE
什么是SSE
很多人可能不知道,服务端向客户端推送消息,其实除了可以用WebSocket这种耳熟能详的机制外,还有一种服务器发送事件(Server-sent events),简称SSE。
SSE它是基于HTTP协议的,我们知道一般意义上的HTTP协议是无法做到服务端主动向客户端推送消息的,但SSE是个例外,它变换了一种思路。SSE在服务器和客户端之间打开一个单向通道,服务端响应的不再是一次性的数据包而是text/event-stream类型的数据流信息,在有数据变更时从服务器流式传输到客户端。
整体的实现思路有点类似于在线视频播放,视频流会连续不断的推送到浏览器,你也可以理解成,客户端在完成一次用时很长(网络不畅)的下载。
SSE与WebSocket作用相似,都可以建立服务端与浏览器之间的通信,实现服务端向客户端推送消息,但还是有些许不同:
- SSE 是基于HTTP协议的,它们不需要特殊的协议或服务器实现即可工作;
WebSocket需单独服务器来处理协议。 - SSE 单向通信,只能由服务端向客户端单向通信;webSocket全双工通信,即通信的双方可以同时发送和接受信息。
- SSE 实现简单开发成本低,无需引入其他组件;WebSocket传输数据需做二次解析,开发门槛高一些。
- SSE 默认支持断线重连;WebSocket则需要自己实现。
- SSE 只能传送文本消息,二进制数据需要经过编码后传送;WebSocket默认支持传送二进制数据。
前端实现
<script>
let source = null;
let userId = 7777
if (window.EventSource) {
// 建立连接
source = new EventSource('http://localhost:7777/sse/sub/'+userId);
setMessageInnerHTML("连接用户=" + userId);
/**
* 连接一旦建立,就会触发open事件
* 另一种写法:source.onopen = function (event) {}
*/
source.addEventListener('open', function (e) {
setMessageInnerHTML("建立连接。。。");
}, false);
/**
* 客户端收到服务器发来的数据
* 另一种写法:source.onmessage = function (event) {}
*/
source.addEventListener('message', function (e) {
setMessageInnerHTML(e.data);
});
} else {
setMessageInnerHTML("你的浏览器不支持SSE");
}
</script>
springboot实现
@RestController
@CrossOrigin
@RequestMapping
public class SSEControler {
//建立之后根据订单id,将SseEmitter存到ConcurrentHashMap
//正常应该存到数据库里面,生成数据库订单,这里我们只是模拟一下
public static final ConcurrentHashMap<Long, SseEmitter> sseEmitters
= new ConcurrentHashMap<>();
//第2步:接受用户建立长连接,表示该用户已支付,已支付就可以生成订单(未确认状态)
@GetMapping("/orderpay")
public SseEmitter orderpay(Long payid) {
//设置默认的超时时间60秒,超时之后服务端主动关闭连接。
SseEmitter emitter = new SseEmitter(60 * 1000L);
sseEmitters.put(payid,emitter);
emitter.onTimeout(() -> sseEmitters.remove(payid));
return emitter;
}
//第3步:接受支付系统的支付结果告知,表明用户支付成功
@GetMapping("/payback")
public void payback (Long payid){
//把SSE连接取出来
SseEmitter emitter = sseEmitters.get(payid);
try {
//第4步:由服务端告知浏览器端:该用户支付成功了
emitter.send("用户支付成功"); //触发前端message事件。
//触发前端自定义的finish事件
emitter.send(SseEmitter.event().name("finish").id("6666").data("哈哈"));
} catch (IOException e) {
emitter.completeWithError(e); //出发前端onerror事件
}
}
}