springboot+websocket+sockjs进行消息推送【基于STOMP协议】_猿人启示录的博客-CSDN博客_websocket+springboot
1配置类
package com.bootdo.websocket.config;
import com.bootdo.charsming.controller.IndexController;
import com.bootdo.websocket.controller.WebSocketController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
/**
* 通过EnableWebSocketMessageBroker
* 开启使用STOMP协议来传输基于代理(message broker)的消息,此时浏览器支持使用@MessageMapping,就像支持@RequestMapping一样。
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* endPoint 注册协议节点,并映射指定的URl
* 注意:客户端应注意断线重连,否则收不到消息咯
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//注册一个名字为"endpointChat" 的endpoint,并指定 SockJS协议。
registry.addEndpoint("/endpointChat").addInterceptors(httphandshakeinterceptor())
// .setAllowedOrigins("*")//跨域
.withSockJS();
}
@Bean
public HttpHandShakeInterceptor httphandshakeinterceptor() {
return new HttpHandShakeInterceptor();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
/**
* 配置消息代理(message broker)
* 1、/user- 点对点
* 2、/topic - 广播
*/
registry.enableSimpleBroker("/user", "/topic");
//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
registry.setUserDestinationPrefix("/user");
}
}
1.1监听
package com.bootdo.websocket.config;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
/**
* @author charsming
* @title: HttpHandShakeIntercepter
* @projectName ZHJQ
* @description: TODO
* @date 2022-02-1314:42
*/
public class HttpHandShakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
}
2.订阅广播
package com.charsming.websocket.controller;
import cn.hutool.core.lang.Console;
import com.charsming.system.service.SessionService;
import com.charsming.websocket.domain.Message;
import com.charsming.websocket.domain.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.security.Principal;
@Controller
public class WebSocketController {
@Autowired
SimpMessagingTemplate template;
@Autowired
SessionService sessionService;
@ResponseBody
@MessageMapping("/welcome") // 浏览器发送请求通过@messageMapping 映射/welcome 这个地址。
@SendTo("/topic/getResponse") //服务器端有消息时,会订阅@SendTo中的路径的浏览器发送消息。
public Response say(Message message) throws Exception {
// template.convertAndSend("/topic/getResponse", new Response("1111111111" ));
return new Response(message.getName());
}
/**
* 发送广播
*/
@ResponseBody
@GetMapping("/testTopic")
public Response testTopic() {
//发送消息到订阅了【/topic/getResponse】的客户端。
template.convertAndSend("/topic/testTopic","测试aaaaaaaaaaaaaaaaaaaaaaa");
return new Response("发送消息到订阅了【/topic/testTopic】的客户端");
}
/**
* 发送单点消息
*/
@ResponseBody
@GetMapping("/testQueue")
public String testQueue(Principal principal, String msg) {
//测试向在线用户发送一条消息
// template.convertAndSendToUser(sessionService.listOnlineUser().get(0).toString(), "/queue/testQueue", principal.getName() + "给您发来了消息:" + msg);
Console.log(sessionService.list().get(0));
template.convertAndSendToUser(sessionService.listOnlineUser().get(0).toString(), "/queue/testQueue", principal.getName() + "给您发来了消息:" + msg);
System.out.println("这个用户是:++++++++++++++0"+principal.getName());
return principal.getName();
}
}
3.监听是否订阅连接断开
3.1
package com.charsming.websocket.listener;
import cn.hutool.core.lang.Console;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.LongAdder;
/**
* 连接数处理
*/
@Component
public class WebSocketConnCounter {
private LongAdder connections = new LongAdder();
public void increment() {
connections.increment();
Console.log("当前连接数:" + connections.sum());
}
public void decrement() {
connections.decrement();
Console.log("当前连接数:" + connections.sum());
}
public long onlineUsers() {
Console.log("当前连接数:" + connections.sum());
return connections.sum();
}
}
3.2
package com.bootdo.websocket.listener;
import cn.hutool.core.lang.Console;
import cn.hutool.db.Entity;
import com.alibaba.fastjson.JSON;
import com.bootdo.otherDB.service.DbGpsService;
import com.bootdo.websocket.constants.ConstantsConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
import java.util.List;
import java.util.concurrent.atomic.LongAdder;
@Component
public class WebSocketEventListener {
//多线程高并发自增计数器
private LongAdder connections = new LongAdder();
@Autowired
SimpMessagingTemplate template;
@Autowired
private DbGpsService DbGpsServiceImp;
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
String sessionId = accessor.getSessionId();
Console.log("======客户端断开提示:======" + sessionId + "已连接");
//增加一个连接
connections.increment();
Console.log("当前连接数:" + connections.sum());
}
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
String sessionId = accessor.getSessionId();
Console.log("======客户端断开提示:======" + sessionId + "已断开");
//增减少一个连接
connections.decrement();
Console.log("当前连接数:" + connections.sum());
}
@EventListener
public void handleWebSocketSubscribeListener(SessionSubscribeEvent event) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
String sessionId = accessor.getSessionId();
//监听订阅地址
String dest = accessor.getDestination();
if (dest.equals(ConstantsConfig.GPS_DB)) {
List<Entity> list = DbGpsServiceImp.getMaxData();
template.convertAndSend(ConstantsConfig.GPS_DB, JSON.toJSON(list));
}
Console.log("======客户端订阅提示:======" + sessionId + "已订阅:" + dest);
}
}
js
<script src="/js/appjs/webSocket/sockjs.min.js"></script>
<script src="/js/appjs/webSocket/stomp.min.js"></script>
<!-- Toastr script -->
<script src="/js/plugins/toastr/toastr.min.js"></script>
//webSocket
function webSocketStart() {
var sock = new SockJS("/endpointChat");
var stomp = Stomp.over(sock);
stomp.connect({} //可添加客户端的认证信息 比如 name: 'test' // 携带客户端信息
, function(frame) {
/**
订阅了"/user/+"userId"+queue/testQueue" 发送的消息,这里与在控制器的 convertAndSendToUser 定义的地址保持一致,
* 这里多用了一个/user,并且这个user 是必须的,使用user 才会发送消息到指定的用户。
* */
// stomp.subscribe("/user/+"userId"+queue/testQueue", handleNotification1);
stomp.subscribe('/topic/charsming/index/countEventLYAndFxDate', handleNotification2);
stomp.subscribe('/topic/bigdata/fy/hsData', handleNotification3);
});
}
function handleNotification2(message) {
// toastr.info(message.body,"成功");
console.log(message.body,"sss")
}
function handleNotification3(message) {
console.log(message.body,"33")
}
3.1参数一:指定客户端接收的用户标识(一般用用户ID)
3.2参数二:客户端监听指定通道时,设定的访问服务器的URL(客户端访问URL跟广播有些许不同)
3.3参数三:向目标发送消息体(实体、字符串等等)
jsDemo2
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
// 连接函数
let number = 1;
function reconnect(socketUrl) {
let url = `${BASE_URL}/ws/sdfpoint`; //连接地址
// 建立连接对象(还未发起连接)
let socket = new SockJS(url);
// 获取 STOMP 子协议的客户端对象
let stompClient = Stomp.over(socket);
// 向服务器发起websocket连接并发送CONNECT帧
stompClient.connect(
{},//可添加客户端的认证信息
function connectCallback (){//连接成功的回调函数
//订阅频道
stompClient.subscribe('/topic/display/control', function(data){
if (data) {
console.log('subscribe data',data);
}
})
},
function errorCallBack(error){
//连接失败时再次调用函数
number += 1;
if(number<=10){
reconnect(url);
}
console.log('error',error);
}
)
}