1. 概念介绍
sse(Server Sent Event),直译为服务器发送事件,顾名思义,也就是客户端可以获取到服务器发送的事件
我们常见的 http 交互方式是客户端发起请求,服务端响应,然后一次请求完毕;但是在 sse 的场景下,客户端发起请求,连接一直保持,服务端有数据就可以返回数据给客户端,这个返回可以是多次间隔的方式
2. 特点分析
SSE 最大的特点,可以简单规划为两个
- 长连接
- 服务端可以向客户端推送信息
3. 应用场景
从 sse 的特点出发,我们可以大致的判断出它的应用场景,需要轮询获取服务端最新数据的 case 下,多半是可以用它的
比如显示当前网站在线的实时人数,法币汇率显示当前实时汇率,电商大促的实时成交额等等...
4. SseEmitter
上面只是简单实现了 sse 的长连接 + 后端推送消息,但是与标准的 SSE 还是有区别的,sse 有自己的规范,而我们上面的实现,实际上并没有管这个,导致的问题是前端按照 sse 的玩法来请求数据,可能并不能正常工作
实现
控制层
package com.whj.servicessepush.controller;
import com.whj.internalcommon.utils.SsmPrefixUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @Auther: wanghaijun
* @Date: 2023/2/23 - 02 - 23 - 15:08
* @Description: com.whj.ssedriverclientweb.controller
*/
@RestController
@Slf4j
public class SseController {
public static Map<String, SseEmitter> sseEmitterMap = new HashMap<>();
/**
* 建立连接
*
* @param userId 建立连接的id
* @param identity 身份类型
* @return
*/
@GetMapping("/connect")
public SseEmitter connect(@RequestParam Long userId, @RequestParam String identity) {
log.info("用户的Id = " + userId, ",身份类型是:" + identity);
SseEmitter sseEmitter = new SseEmitter(0l);
String sseMapKey = SsmPrefixUtils.generatorSseKey(userId, identity);
//将收到的消息存放到静态的map中
sseEmitterMap.put(sseMapKey, sseEmitter);
return sseEmitter;
}
/**
* 发送连接
*
* @param userId 连接的用户
* @param identity 身份类型
* @param content 消息的类容
* @return
*/
@GetMapping("/push")
public String push(@RequestParam Long userId, @RequestParam String identity, @RequestParam String content) {
String sseMapKey = SsmPrefixUtils.generatorSseKey(userId, identity);
try {
if (sseEmitterMap.containsKey(sseMapKey)) {
sseEmitterMap.get(sseMapKey).send(content);
} else {
return "推送失败";
}
} catch (IOException e) {
e.printStackTrace();
}
return "给用户:" + sseMapKey + "发送了消息:" + content;
}
/**
* 移除sseEmitterMap发来的值 关闭连接
*
* @param userId 关闭连接的用户
* @param identity 身份类型
* @return
*/
@GetMapping("/close")
public String close(@RequestParam Long userId, @RequestParam String identity) {
String sseMapKey = SsmPrefixUtils.generatorSseKey(userId, identity);
log.info("关闭连接:" + sseMapKey);
if (sseEmitterMap.containsKey(sseMapKey)) {
sseEmitterMap.remove(sseMapKey);
}
return "close 成功!";
}
}
司机:监听页面测试
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>司机:监听页面测试-模拟监听客户端</h1>
<button onclick="setMessageContent('鼠标点我了')">测试message</button>
<div id="message">展示服务端推送过来的消息</br></div>
<button onclick="sourceClose()">关闭连接</button>
<script>
// 连接传递的值
userId="1631130415052435458";
identity = 1;
source = null;
if(window.EventSource){
console.info("此浏览器支持SSE");
// 连接的建立
source = new EventSource("http://localhost:9000/connect?userId="+userId+"&identity="+identity);
// 监听服务的推送的消息
source.addEventListener("message",function (e){
content = e.data;
console.info("消息内容:"+content);
setMessageContent(content);
});
}else {
setMessageContent("此浏览器不支持");
}
function setMessageContent(content){
document.getElementById("message").innerHTML+= (content+'</br>');
}
function sourceClose(){
console.info("close方法执行");
// 客户端source的关闭
source.close();
// 服务端map的移除
httpRequest = new XMLHttpRequest();
httpRequest.open("get","http://localhost:9000/close?userId="+userId+"&identity="+identity);
httpRequest.send();
}
</script>
</body>
</html>
乘客:监听测试页面
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>乘客:监听测试页面-模拟监听客户端</h1>
<button onclick="setMessageContent('鼠标点我了')">测试message展示</button>
<div id="message">展示服务的推送过来消息的地方</br></div>
<button onclick="sourceClose()">关闭连接</button>
<script>
userId="1631471212960489474";
identity = 1;
source = null;
if(window.EventSource){
console.info("此浏览器支持SSE");
// 连接的建立
source = new EventSource("http://localhost:9000/connect?userId="+userId+"&identity="+identity);
// 监听服务的推送的消息
source.addEventListener("message",function (e){
content = e.data;
console.info("消息内容:"+content);
setMessageContent(content);
});
}else {
setMessageContent("此浏览器不支持");
}
function setMessageContent(content){
document.getElementById("message").innerHTML+= (content+'</br>');
}
function sourceClose(){
console.info("close方法执行");
// 客户端source的关闭
source.close();
// 服务端map的移除
httpRequest = new XMLHttpRequest();
httpRequest.open("get","http://localhost:9000/close?userId="+userId+"&identity="+identity);
httpRequest.send();
}
</script>
</body>
</html>