当前位置: 首页 > news >正文

websocket实践

springboot集成websocket

  • 写在前面
  • 1. 后端
    • 1.1 配置类(一般放在config文件夹下)
    • 1.2 构造的消息实体
    • 1.3 工具类(辅助消息处理类)
    • 1.4 消息处理类
  • 2. 前端页面
  • 3. 最终效果
  • 4. 可能坑点
  • 5. 拓展
  • 总结
  • 写在最后

写在前面

此功能是基于分离版的若依系统实践,技术为springboot+websocket+vue,功能主要实现定时(每一分钟)给前台推送一条消息,前台显示推送的消息。

1. 后端

1.1 配置类(一般放在config文件夹下)

  • ServerEndpointExporter 是一个类,属于 javax.websocket.server 包的一部分。
  • 它的主要作用是自动扫描 Spring 容器中带有 @ServerEndpoint 注解的类,并将它们注册为 WebSocket 端点。
  • 当我们在一个类上使用 @ServerEndpoint 注解时,必须通过某种方式将这个类注册到 WebSocket 容器中。ServerEndpointExporter 的作用就是自动完成这个注册过程。
package com.ruoyi.framework.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {/*** ServerEndpointExporter 是一个类,属于 javax.websocket.server 包的一部分。* 它的主要作用是自动扫描 Spring 容器中带有 @ServerEndpoint 注解的类,并将它们注册为 WebSocket 端点。* 当我们在一个类上使用 @ServerEndpoint 注解时,必须通过某种方式将这个类注册到 WebSocket 容器中。ServerEndpointExporter 的作用就是自动完成这个注册过程。* @return*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

1.2 构造的消息实体

package com.ruoyi.framework.ws.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class MessageData {private String nickname;  // 发送者昵称private String content;   // 消息内容}

1.3 工具类(辅助消息处理类)

SemaphoreUtils类:

  • 信号量相关处理
  • 在WebSocket 应用中,限制同时连接的客户端数量
package com.ruoyi.framework.ws.util;import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 信号量相关处理* 在WebSocket 应用中,限制同时连接的客户端数量* */
public class SemaphoreUtils
{/*** SemaphoreUtils 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class);/*** 获取信号量** @param semaphore* @return*/public static boolean tryAcquire(Semaphore semaphore){boolean flag = false;try{flag = semaphore.tryAcquire();}catch (Exception e){LOGGER.error("获取信号量异常", e);}return flag;}/*** 释放信号量** @param semaphore*/public static void release(Semaphore semaphore){try{semaphore.release();}catch (Exception e){LOGGER.error("释放信号量异常", e);}}
}

WebSocketUsers类

  • websocket 客户端用户集
  • 封装用户会话的管理、群发和单发消息操作,简化了 WebSocket 应用的开发
package com.ruoyi.framework.ws;import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** websocket 客户端用户集* 封装用户会话的管理、群发和单发消息操作,简化了 WebSocket 应用的开发**/
public class WebSocketUsers
{/*** WebSocketUsers 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);/*** 用户集*/private static Map<String, Session> USERS = new ConcurrentHashMap<String, Session>();/*** 存储用户** @param key 唯一键* @param session 用户信息*/public static void put(String key, Session session){USERS.put(key, session);}/*** 移除用户** @param session 用户信息** @return 移除结果*/public static boolean remove(Session session){String key = null;boolean flag = USERS.containsValue(session);if (flag){Set<Map.Entry<String, Session>> entries = USERS.entrySet();for (Map.Entry<String, Session> entry : entries){Session value = entry.getValue();if (value.equals(session)){key = entry.getKey();break;}}}else{return true;}return remove(key);}/*** 移出用户** @param key 键*/public static boolean remove(String key){LOGGER.info("\n 正在移出用户 - {}", key);Session remove = USERS.remove(key);if (remove != null){boolean containsValue = USERS.containsValue(remove);LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功");return containsValue;}else{return true;}}/*** 获取在线用户列表** @return 返回用户集合*/public static Map<String, Session> getUsers(){return USERS;}/*** 群发消息文本消息** @param message 消息内容*/public static void sendMessageToUsersByText(String message){Collection<Session> values = USERS.values();for (Session value : values){sendMessageToUserByText(value, message);}}/*** 发送文本消息** @param session 缓存* @param message 消息内容*/public static void sendMessageToUserByText(Session session, String message){if (session != null){try{session.getBasicRemote().sendText(message);}catch (IOException e){LOGGER.error("\n[发送消息异常]", e);}}else{LOGGER.info("\n[你已离线]");}}
}

1.4 消息处理类

package com.ruoyi.framework.ws;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;import com.ruoyi.framework.ws.util.SemaphoreUtils;
import com.ruoyi.framework.ws.util.WebSocketMessageScheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/*** websocket 消息处理**/
@Component
@ServerEndpoint("/websocket/message")
public class WebSocketServer
{/*** WebSocketServer 日志控制器*/private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);/*** 默认最多允许同时在线人数100*/public static int socketMaxOnlineCount = 100;private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);private static Map<String, WebSocketMessageScheduler> schedulers = new ConcurrentHashMap<>();/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session) throws Exception{boolean semaphoreFlag = false;// 尝试获取信号量semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);if (!semaphoreFlag){// 未获取到信号量LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);session.close();}else{// 添加用户WebSocketUsers.put(session.getId(), session);WebSocketMessageScheduler scheduler = new WebSocketMessageScheduler(session);scheduler.start();schedulers.put(session.getId(), scheduler);LOGGER.info("\n 建立连接 - {}", session);LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size());WebSocketUsers.sendMessageToUserByText(session, "连接成功11111111");}}/*** 连接关闭时处理*/@OnClosepublic void onClose(Session session){LOGGER.info("\n 关闭连接 - {}", session);// 移除用户WebSocketUsers.remove(session.getId());WebSocketMessageScheduler scheduler = schedulers.remove(session.getId());if (scheduler != null) {scheduler.stop();}// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 抛出异常时处理*/@OnErrorpublic void onError(Session session, Throwable exception) throws Exception{if (session.isOpen()){// 关闭连接session.close();}String sessionId = session.getId();WebSocketMessageScheduler scheduler = schedulers.remove(sessionId);if (scheduler != null) {scheduler.stop();}LOGGER.info("\n 连接异常 - {}", sessionId);LOGGER.info("\n 异常信息 - {}", exception);// 移出用户WebSocketUsers.remove(sessionId);// 获取到信号量则需释放SemaphoreUtils.release(socketSemaphore);}/*** 服务器接收到客户端消息时调用的方法*/@OnMessagepublic void onMessage(String message, Session session){String msg = message.replace("你", "我").replace("吗", "");WebSocketUsers.sendMessageToUserByText(session, msg);}}

2. 前端页面

在若依的菜单管理随便创建一个页面进行测试

<template><div class="websocket-container"><h1>WebSocket 消息接收页面</h1><div class="message-area"><h2>消息列表</h2><ul class="message-list"><li v-for="(message, index) in messageList" :key="index" class="message-item">{{ message }}</li></ul></div><div class="connection-status"><p>连接状态: {{ connectionStatus }}</p></div><button @click="closeWebSocket" class="close-button">关闭连接</button></div>
</template><script>
export default {data() {return {ws: null,messageList: [],connectionStatus: '未连接'};},mounted() {this.initWebSocket();},beforeDestroy() {if (this.ws) {this.ws.close();}},methods: {initWebSocket() {const wsUrl = 'ws://127.0.0.1:8080/websocket/message'; // 替换为你的 WebSocket 服务器地址this.ws = new WebSocket(wsUrl);this.ws.onopen = () => {this.connectionStatus = '已连接';console.log('WebSocket 连接已建立');};this.ws.onerror = (error) => {this.connectionStatus = '连接错误';console.error('WebSocket 错误:', error);};this.ws.onmessage = (event) => {const message = event.data;this.messageList.push(message);console.log('接收到消息:', message);};this.ws.onclose = () => {this.connectionStatus = '已断开';console.log('WebSocket 连接已关闭');};},closeWebSocket() {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.close();this.connectionStatus = '已手动关闭';}}}
};
</script><style scoped>
.websocket-container {max-width: 800px;margin: 0 auto;padding: 20px;font-family: Arial, sans-serif;
}.message-area {margin-top: 20px;
}.message-list {list-style-type: none;padding: 0;margin: 0;max-height: 400px;overflow-y: auto;border: 1px solid #ddd;border-radius: 4px;
}.message-item {padding: 10px;margin-bottom: 5px;background-color: #f9f9f9;border-radius: 4px;
}.connection-status {margin-top: 20px;padding: 10px;background-color: #e9f7ef;border-radius: 4px;
}.close-button {margin-top: 20px;padding: 10px 20px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;
}.close-button:hover {background-color: #45a049;
}
</style>

3. 最终效果

在这里插入图片描述

4. 可能坑点

  1. 如果和我一样基于若依,记得给/websocket/**放行(在SecurityConfig中)
    在这里插入图片描述

  2. 可以使用ApiFox测试websocket是否连接成功
    ApiFox有专门的测试websocket接口,记得加上token在这里插入图片描述

5. 拓展

基于这个的成功,我紧接着实现了一个基于websocket简单的聊天应用,效果图如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

写这篇一是自己加深记忆,其次就是共享。

写在最后

如果此文对您有所帮助,请帅戈靓女们务必不要吝啬你们的Zan,感谢!!不懂的可以在评论区评论,有空会及时回复。

相关文章:

  • (41)课60--61高级篇: MySQL体系结构(连接层、服务层、引擎层、存储层)。存储引擎是基于表的,可为不同表指定不同的存储引擎;查询表结构语句 show create table 表名
  • 鹰盾加密器“一机一码”技术全维度剖析:从底层实现到生态防护体系
  • 计算机网络面试汇总(完整版)
  • HTML 、CSS 、JavaScript基本简单介绍
  • docker详细操作--未完待续
  • TDengine 快速体验(Docker 镜像方式)
  • 手写muduo网络库(三):事件分发器(Poller,EPollPoller实现)
  • 邮科OEM摄像头图像处理技术:从硬件协同到智能进化
  • 微软PowerBI考试 PL300-在 Power BI 中设计语义模型 【附练习数据】
  • 高考倒计时(vb.net,持续更新版本)
  • 7.3.折半查找(二分查找)
  • Leetcode 3578. Count Partitions With Max-Min Difference at Most K
  • Oracle SQL*Plus 配置上下翻页功能
  • 行为设计模式之Memento(备忘录)
  • Linux 删除登录痕迹
  • 多面体编译的循环分块
  • 字符串方法_indexOf() +_trim()+_split()
  • 定制化平板电脑在各行业中有哪些用途与作用?
  • CppCon 2015 学习:Give me fifteen minutes and I’ll change your view of GDB
  • 【Java多线程从青铜到王者】懒汉模式的优化(九)
  • 青岛建设银行股份有限公司网站首页/关键词指数查询
  • 广州做网站多/合肥最新消息今天
  • 做网站宿迁/app营销推广方案
  • 网站低保图用什么做/网站功能优化的方法
  • asp网站建设mdb文件/四川聚顺成网络科技有限公司
  • 神网站建设/软件开发公司