Spring应用-2-SpringBoot+WebSocket实现后端向前端主动发送信息
1. 前言
- 需求:
客户端启动后在执行某些命令式,需要长期、持续、及时的获取服务端产生的日志数据。此时就需要用到WebSocket的后端主动往指定前端发送消息的技术来实现。 - 技术原理:
下面引用文章的大佬已经给出讲解,这里不过多赘述。 - 前置
本文是基于 《Spring应用-1-SpringBoot+Thymeleaf+Vue快速搭建前台项目》 扩展实现的。 - 鸣谢
2. 引入和配置
- SpringBoot已经对WebSocket技术实现了自动配置和版本管理,只要导入标签即可。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> - 启用WebSocket
光引入了WebSocket是不够的,还需要在项目中开启。创建一个名为WebSocketConfig的配置类。import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
3. 后端工具类方法
- 开启了WebSeocket功能,也需要一系列的方法来使用。创建名为WebSocketServer的业务类,记录连接的客户端、处理客户端发来的数据和往客户端发送数据。
import org.springframework.stereotype.Component; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; @Component // 类似@RequestMapping注解,只不过这个只支持ws协议,不用http/https // 这里的id是发送请求时url最后标记客户端的标识,说明这是一个ws的通用入口,内部不会细分 @ServerEndpoint("/wsserver/{id}") public class WebSocketServer { // 日志一定要有,这里用的是log4j2+slf4j private static Logger logger = LoggerFactory.getLogger(WebSocketServer.class); // 连接数量(感觉可有可无,目前并无用到数量限制之类的) private static int onlineCount = 0; // 线程安全的方式存放客户端对象 private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>(); // 存储客户端会话 private Session session; // 记录会话ID private String id = ""; // 创建连接 @OnOpen public void onOpen(Session session, @PathParam("id") String id) { // 记录当前请求连接的客户端 this.session = session; this.id = id; // 如果在已登陆列表中找到,需要删除老session信息,重新记录 if (webSocketMap.containsKey(id)) { webSocketMap.remove(id); webSocketMap.put(id, this); } else { // 全新用户直接填充 webSocketMap.put(id, this); this.addOnlineConunt(); } logger.info("WebSocket:用户[" + id + "]已连接到WS服务器!"); // 此时可以给客户端发送连接成功的信息 try { JSONObject result = new JSONObject(); result.put("type", "register"); // 发送消息 sendMessage(result.toJSONString); } catch (IOException e) { logger.error("WebSocket:用户网络异常,发送失败!"); } } // 接收客户端消息 @OnMessage public void onMessage(String message, Session session) { logger.info("WebSocket:接收到用户[" + id + "]发送消息,报文:" + message); if (StringUtils.isNotBlank(message)) { // 解析报文内容,是发给服务端的还是发给其他客户端的 try { JSONObject msgJo = JSON.parseObject(message); if (StringUtils.isNotBlank(msgJo.getString("toId")) { // 发给其他客户端的 msgJo.put("fromId", this.id); String toId = msgJo.getString("toId"); if (webSocketMap.containsKey(toId)) { webSocketMap.get(toId).sendMessage(msgJo.toJSONString()); } else { logger.error("WebSocket:目标ID[" + toId + "]不存在,发送失败!"); } } else { // 发给服务端的 } } catch (Exception e) { e.printStackTrace(); } } } // 服务端主动推送信息 public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } // 向指定客户端发送消息 public static void sendInfo(String message, @PathParam("id") String id) throws IOException { if (StringUtils.isNotBlank(id) && webSocketMap.containsKey(id)) { webSocketMap.get(id).sendMessage(message); } else { logger.error("WebSocket:目标ID[" + id + "]不存在,发送失败!") } } // 异常处理 @OnError public void onError(Session session, Throwable error) { logger.error("WebSocket:用户错误,原因:" + error.toString()); error.printStackTrace(); } // 客户端主动断开连接 @OnClose public void onClose() { if (webSocketMap.containsKey(id)) { webSocketMap.remove(id); subOnlineCount(); } logger.info("WebSocket:用户[" + id + "]已断开WS服务器连接!"); } // 在线数量 public static synchronized int getOnLineCount() { return onlineCount; } // 增加在线数量 public static synchronized void addOnlineCount() { WebSocketServer.onLineCount++; } // 减少在线数量 public static synchronized void subOnlineCount() { WebSocketServer.onLineCount--; } } - 使用也很简单,因为是静态方法,直接使用即可。
public void testSendMsg() { JSONObject msg = new JSONObject(); msg.put("type", "test"); msg.put("msg", "服务端测试发送信息"); try { WebSocketServer.sendInfo(msg.toJSONString(), "1"); } catch (IOException e) { logger.error("WebSocket:发送测试消息失败!原因:" + e.toString()); } }
4. 客户端应用
这里的示例是基于Vue的代码示例。
new Vue({
el: '#app',
data: {
// 事先需要向服务端请求一个id,来唯一标识客户端
id: '',
// 服务端地址,一般都是网关的地址
wsUrl: 'ws://127.0.0.1:8081/wsserver/',
// WebSocket实例
webSocket: null
},
methods: {
// 初始化WebSocket
initWebSocket() {
if (typeof WebSocket === 'undefined') {
this.$message.error('Error:浏览器不支持WebSocket!');
return;
}
// 创建WebSocket实例,创建以前先检查一下id是否获取到
this.webSocket = new WebSocket(this.wsUrl + this.id);
// 给WebSocket实例提供实现方法
this.webSocket.onmessage = this.webSocketOnMessage;
this.webSocket.onopen = this.webSocketOnOpen;
this.webSocket.onerror = this.webSocketOnError;
this.webSocket.onclose = this.webSocketClose;
},
// 连接WebSocket服务器
webSocketOnOpen() {
this.$message({
message: 'WebSocket服务器连接成功!',
type: 'success'
});
},
// 连接失败时进行重连
webSocketOnError() {
this.initWebSocket();
},
// 接收到数据
webSocketOnMessage(res) {
const resData = JSON.parse(res.data);
// 根据收到的信息做不同的操作
},
// 向WebSocket服务器发送信息
webSocketSend(data) {
this.webSocket.send(data);
},
// 关闭WebSocket连接
webSocketClose(e) {
this.$message.error('WebSocket服务器连接断开!');
},
// 模拟发送
testSendMsg() {
let msg = {msg: '这是一条测试信息,发给服务器', toId: ''};
this.webSocketSend(JSON.stringify(msg));
}
},
created: function () {
// 同步处理先获取id,再创建WebSocket连接
this.initWebSocket();
},
destroyed: function () {
// 在Vue销毁前断开WebSocket服务器连接
this.webSocket.close();
}
})

浙公网安备 33010602011771号