SSE简介
SSE(Server Sent Event),直译为服务器发送事件,也就是服务器主动发送事件,客户端可以获取到服务器发送的事件。
SSE简单来说就是服务器主动向前端推送数据的一种技术,它是单向的。SSE适用于消息推送,监控等只需要服务器推送数据的场景中。比如:文件下载时,后端可以推送下载进度条信息。
SSE相关依赖
<!--web依赖,内嵌入tomcat,SSE依赖于该jar包,只要有该依赖就能使用SSE-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SSE工具类代码
@Slf4j
public class SseEmitterServer {
/**
* 当前连接数
*/
private static AtomicInteger count = new AtomicInteger(0);
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
public static SseEmitter connect(String userId){
//设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常
SseEmitter sseemitter = new SseEmitter(0L);
//注册回调
sseemitter.onCompletion(completionCallBack(userId));
//这个onError在springbooot低版本没有这个方法,公司springboot1.4.2版本,没有这个方法,可以进行注释。
sseemitter.onError(errorCallBack(userId));
sseemitter.onTimeout(timeoutCallBack(userId));
sseEmitterMap.put(userId,sseemitter);
//数量+1
count.getAndIncrement();
log.info("create new sse connect ,current user:{}",userId);
return sseemitter;
}
/**
* 给指定用户发消息
*/
public static void sendMessage(String userId, String message){
if(sseEmitterMap.containsKey(userId)){
try{
sseEmitterMap.get(userId).send(message);
}catch (IOException e){
log.error("user id:{}, send message error:{}",userId,e.getMessage());
e.printStackTrace();
}
}
}
/**
* 想多人发送消息,组播
*/
public static void groupSendMessage(String groupId, String message){
if(sseEmitterMap!=null&&!sseEmitterMap.isEmpty()){
sseEmitterMap.forEach((k,v) -> {
try{
if(k.startsWith(groupId)){
v.send(message, MediaType.APPLICATION_JSON);
}
}catch (IOException e){
log.error("user id:{}, send message error:{}",groupId,message);
removeUser(k);
}
});
}
}
public static void batchSendMessage(String message) {
sseEmitterMap.forEach((k,v)->{
try{
v.send(message,MediaType.APPLICATION_JSON);
}catch (IOException e){
log.error("user id:{}, send message error:{}",k,e.getMessage());
removeUser(k);
}
});
}
/**
* 群发消息
*/
public static void batchSendMessage(String message, Set<String> userIds){
userIds.forEach(userid->sendMessage(userid,message));
}
//移除用户
public static void removeUser(String userid){
sseEmitterMap.remove(userid);
//数量-1
count.getAndDecrement();
log.info("remove user id:{}",userid);
}
public static List<String> getIds(){
return new ArrayList<>(sseEmitterMap.keySet());
}
public static int getUserCount(){
return count.intValue();
}
private static Runnable completionCallBack(String userId) {
return () -> {
log.info("结束连接,{}",userId);
removeUser(userId);
};
}
private static Runnable timeoutCallBack(String userId){
return ()->{
log.info("连接超时,{}",userId);
removeUser(userId);
};
}
private static Consumer<Throwable> errorCallBack(String userId){
return throwable -> {
log.error("连接异常,{}",userId);
removeUser(userId);
};
}
}
Controller测试层
@RestController
@RequestMapping(value = "/test")
public class TestController {
//sse连接接口
@GetMapping (value = "/sse/connect/{id}")
public SseEmitter connect(@PathVariable String id){
SseEmitter connect = SseEmitterServer.connect(id);
//连接成功之后通知前端连接成功
SseEmitterServer.sendMessage(id,"链接成功");
return connect;
}
//sse向指定用户发送消息接口
@GetMapping (value = "/sse/send/{id}")
public Map<String,Object> send(@PathVariable String id,@RequestParam(value = "message", required = false) String message){
Map<String,Object> returnMap = new HashMap<>();
//向指定用户发送信息
SseEmitterServer.sendMessage(id,message);
returnMap.put("message","向id为"+id+"的用户发送:"+message+"成功!");
returnMap.put("status","200");
returnMap.put("result",null);
return returnMap;
}
//sse向所有已连接用户发送消息接口
@GetMapping (value = "/sse/batchSend")
public Map<String,Object> batchSend(@RequestParam(value = "message", required = false) String message){
Map<String,Object> returnMap = new HashMap<>();
//向指定用户发送信息
SseEmitterServer.batchSendMessage(message);
returnMap.put("message",message+"消息发送成功!");
returnMap.put("status","200");
returnMap.put("result",null);
return returnMap;
}
//sse关闭接口
@GetMapping (value = "/sse/close/{id}")
public Map<String,Object> close(@PathVariable String id){
Map<String,Object> returnMap = new HashMap<>();
//移除id
SseEmitterServer.removeUser(id);
System.out.println("当前连接用户id:"+SseEmitterServer.getIds());
returnMap.put("message","连接关闭成功!");
returnMap.put("status","200");
returnMap.put("result",null);
return returnMap;
}
}
Apifox测试效果
测试连接接口: 如下图客户端主动连接服务端成功!
服务端推送消息给客户端
测试群发功能(这时候我们在使用一个请求连接到SSE服务端,并指定不同的连接id)
后端打印当前连接成功的用户:
群发测试:
此时两个连接成功的SSE客户端都能收到服务端推送的消息内容。
关闭连接的接口测试
如果有什么问题欢迎大家私信,也可以互相交流,私信必回