Server-Sent Events向前端消息推送技术
创作时间:
作者:
@小白创作中心
Server-Sent Events向前端消息推送技术
引用
简书
1.
https://www.jianshu.com/p/942e1b5223b3
什么是SSE技术
Server-Sent Events (SSE) 是一种用于服务端向客户端单向实时通信的Web技术。它适用于消息通知的场景,能够实现实时数据推送。
SSE技术优缺点
优点
- 实时性
- SSE 一般只用来传送文本,二进制数据需要编码后传送
- SSE 使用 HTTP 协议,只是content-type标识数据类型为
text/event-stream
缺点
- 单向通信
- 老旧浏览器无法兼容
- 无法跨域
SSE通信本质
- 前端向后端发起连接请求,建立连接后会一直保持连接,后端会一直发送数据流到前端。前端关闭连接,就断开连接。
- 前端添加事件监听。
- 前端就可以接收到后端消息推送。
Spring Boot实现SSE技术
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.13</version>
</dependency>
代码
/**
* 消息模型
*/
public class MessageInfo {
private String userNo;
private String message;
private String sendTime;
}
/**
* SSE服务实现
*/
public class SseEmitterServer {
/**
* 统计SSE在线连接数
*/
private static AtomicInteger count = new AtomicInteger(0);
/**
* 客户标识符和消息推送映射关系
*/
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
/**
* 建立连接
* @param userNo
* @return
*/
public static SseEmitter connect(String userNo) {
// 设置超时日期,0表示不过期
SseEmitter sseEmitter = new SseEmitter(0L);
sseEmitter.onCompletion(() -> {
log.info("结束连接 userNo = " + userNo);
});
sseEmitter.onError(throwable -> {
log.info("连接异常 userNo = " + userNo);
removeUser(userNo);
});
sseEmitter.onTimeout(() -> {
log.info("连接超时 userNo = " + userNo);
removeUser(userNo);
});
sseEmitterMap.put(userNo, sseEmitter);
count.getAndIncrement();
log.info("创建新SSE连接,userNo = " + userNo);
return sseEmitter;
}
/**
* 移出用户
* @param userNo
*/
public static void removeUser(String userNo){
sseEmitterMap.remove(userNo);
count.getAndDecrement();
log.info("移除的 userNo = " + userNo);
log.info("剩余的 userNo集合 = " + sseEmitterMap.keySet());
}
/**
* 向指定的userNo发送消息message
* @param userNo
* @param message
*/
public static void sendMessageToUserNo(String userNo, String message) {
if (!sseEmitterMap.containsKey(userNo)) {
connect(userNo);
}
try {
sseEmitterMap.get(userNo).send(message);
log.info("向 userNo = " + userNo + "发送消息,内容如下:" + message);
} catch (IOException e) {
log.error("向 userNo = " + userNo + "发送消息出错,错误内容如下:" + e.getMessage());
e.printStackTrace();
}
}
}
@Controller
@RequestMapping("/sse")
public class SseController {
/**
* 接收连接
* @param userNo
* @return
*/
@GetMapping("/connect/{userNo}")
public SseEmitter connect( String userNo){
SseEmitter sseEmitter = SseEmitterServer.connect(userNo);
return sseEmitter;
}
/**
* 关闭连接
* @param userNo
* @return
*/
@GetMapping("/close/{userNo}")
public String closeSse( String userNo) {
SseEmitterServer.removeUser(userNo);
return "关闭 userNo = " + userNo + "连接";
}
/**
* 发送消息
* @param userNo
* @param messageInfo
* @return
*/
@PostMapping("/send/{userNo}")
public String sendMessageToUserNo( String userNo, @RequestBody MessageInfo messageInfo) {
if (Objects.isNull(messageInfo) || StrUtil.isBlank(messageInfo.getMessage())) {
log.error("消息内容不能为空!");
return "消息内容不能为空!";
}
messageInfo.setUserNo(userNo);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String nowTime = LocalDateTime.now().format(dtf);
messageInfo.setSendTime(nowTime);
// 向SSE前端发送消息
String jsonStr = JSONUtil.toJsonStr(messageInfo);
SseEmitterServer.sendMessageToUserNo(userNo, jsonStr);
return "向 userNo = " + userNo + "发送消息,内容为:" + jsonStr;
}
}
前端实现SSE技术
前端封装sse的js代码:
let eventSource = null;
export function createConnection() {
let url = "http://localhost:3831/sse/connect/123456";
let headers = {};
eventSource = new EventSource(url, headers);
}
export function addListener() {
eventSource.onopen = () => {
console.log("eventSource.onopen 连接畅通");
}
eventSource.onmessage = (event) => {
console.log("eventSource.onmessage 收到消息 event.data = " + event.data);
eventSource.close();
console.log("收到消息后关闭连接");
}
eventSource.onerror = (err) => {
console.log("eventSource.onerror 发生错误 err = " + err);
}
}
前端页面代码:
<template>
<div class="hello">
<el-row style="margin-top:10px;">
<el-col :span="12">
<el-button type="primary" @click="openAndRecvForSSE">点击</el-button>
</el-col>
</el-row>
</div>
</template>
<script>
import {createConnection, addListener} from "@/utils/sse.js";
export default {
name: 'HelloWorld',
data () {
return {
}
},
watch: {},
mounted() {},
created() { },
methods: {
openAndRecvForSSE() {
createConnection();
addListener();
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
测试结果
- 点击按钮,建立连接,前端添加监听事件,后端与前端成功建立连接。
- 使用postman发送消息给Controller,其中使用SSE消息通知到前端。
抓包查看SSE
参考
热门推荐
如何走出失恋的痛苦,重新拥抱生活
陆游笔下的铁马冰河:历史与情感交织
苏州北站改扩建获批,长三角一体化再添新引擎
苏州北站改扩建获批,长三角一体化再添新引擎
苏州北站改扩建,最新出行攻略出炉!
嵩山少林寺&白云山:春节最美自然景观推荐!
2025河南春节新玩法全攻略:五大主会场接力开启,六大主题板块打造节日新风尚
老家河南过大年的那些必打卡景点
圣母百花大教堂:世界最大穹顶见证文艺复兴辉煌
佛罗伦萨:文艺复兴的摇篮
清明上河园:一座穿越千年的古典园林
科学护理头皮,从选择洗发水开始
粤港大湾区两地车牌:概念、分类、与澳车牌的区别
江苏沉寂千年的老城,安静而秀美的龙城常州,你了解过它吗?
压岁钱大作战:如何让压岁钱既有趣又有意义?
西安湖南春节活动大比拼:谁更火?
春节探秘广府古城与少林寺的文化之旅
养老的最佳方式:同一小区,3位80岁以上老人正在经历的养老过程
农文旅融合振兴民族村
《Infection Free Zone》资源管理完全攻略
《原神》5.3版本革新:六大部族与火神降临,开放世界重获新生
赫哲族传统节日:乌日贡节、春节、河灯节等的风俗活动
内蒙古乌兰浩特市乌兰哈达镇三合村:特色文旅风韵浓
黑龙江省抚远市乌苏镇抓吉赫哲族村:渔歌唱出振兴曲
长江十年禁渔:如何在武汉河下头垂钓时保护生态?
范成大笔下的田园生活,你向往吗?
范成大笔下的农耕文化:从诗句到精神传承
四川凉山:螺髻山与邛海,你更爱哪个?
探访凉山:红色之旅的文化盛宴
九寨沟什么时候最美?什么时候去九寨沟最好