springboot 分布式 项目下WebSocket+RabbitMq的搭建
本文仅仅是个人工作上用到的知识点的备忘笔记
前提:支持RabbitMq运行的环境
在搭建rabbitmq之前,得先搭建他的运行环境,传送阵法biubiubiu:
https://blog.csdn.net/weixin_43738469/article/details/106195105
必不可少的导包,在pom文件中引入下面的包
1 2 3 4 5 6 7 8 9 10 | <!-- rabbitMq 的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <!-- websocket 的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> |
1.websocket配置:新建WebSocketConfig类,用来支持websocket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * 开启WebSocket支持 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } } |
2.rabbitmq配置:新建FanoutRabbitConfig类,用来定义队列并绑定交换机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FanoutRabbitConfig { /** * 定义队列A * @return */ @Bean public Queue aMessage() { return new Queue( "q_fanout_A" ); } /** * 定义队列B * @return */ @Bean public Queue bMessage() { return new Queue( "q_fanout_B" ); } /** * 定义交换机 * @return */ @Bean FanoutExchange fanoutExchange() { return new FanoutExchange( "myfanoutExchange" ); } /** * 将队列A和交换机进行绑定 * @return */ @Bean Binding bindingExchangeA() { return BindingBuilder.bind(aMessage()).to(fanoutExchange()); } /** * 将队列B和交换机进行绑定 * @return */ @Bean Binding bindingExchangeB() { return BindingBuilder.bind(bMessage()).to(fanoutExchange()); } } |
3.新建WebSocketServer类结合RabbitMq,实现实时消息推送
这个方法可以获取到mq队列中的消息,我这里是将要接收消息的用户id在我的后台方法中拼接成一个字符串,然后放进mq队列中去,这个方法中获取到mq队列中的信息之后,将他进行处理成集合然后与websocket中的登陆用户进行对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | package com.medicine.management_side.controller; import org.apache.commons.collections.map.HashedMap; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端 * * @RabbitListene注解是用来接收某个队列中的消息,如果该队列有消息,就给@RabbitHandle注解下的方法 */ @RabbitListener (queues = "q_fanout_A" ) @ServerEndpoint (value = "/websocket" ) @Component public class WebSocketServer { //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0 ; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 //private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>(); private static ConcurrentHashMap<String, WebSocketServer> webSocketSet = new ConcurrentHashMap<>(); /** * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 */ private static Map<String, Session> sessionPools = new HashMap<>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; /** * 连接建立成功调用的方法 * * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void onOpen(Session session) { //这个地方我是从session中获取登陆id,然后作为map中的key,session作为value //方法我觉得怪怪的,还得循环遍历,哪位大佬要是有什么好的意见给我指点指点 this .session = session; List<String> uid = session.getRequestParameterMap().get( "uid" ); if (!CollectionUtils.isEmpty(uid)) { for (String id : uid) { webSocketSet.put(id, this ); //加入set中 addOnlineCount(); //在线数加1 } } System.out.println( "有新连接加入!当前在线人数为" + getOnlineCount()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(Session session) { List<String> uid = session.getRequestParameterMap().get( "uid" ); if (!CollectionUtils.isEmpty(uid)) { for (String id : uid) { webSocketSet.remove(id); //从set中删除 subOnlineCount(); //在线数减1 System.out.println( "有一连接关闭!当前在线人数为" + getOnlineCount()); } } } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 可选的参数 */ @OnMessage public void onMessage(String message, Session session) { Map<String, String> userIdMap = new HashedMap(); System.out.println( "来自客户端的消息:" + message); try { //这个地方的admin是写死的,我的需求是消息推送,如果用户阅读了消息,我这里直接在前端调用这个方法传admin进来,然后就可以实时更新前端数据了 if ( "admin" .equals(message)) { //阅读类型的 for (String key : webSocketSet.keySet()) { webSocketSet.get(key).sendMessage(message); } } else { String[] messageAry = message.split( "," ); for (String id : messageAry) { userIdMap.put(id, id); } //群发消息 for (String key : webSocketSet.keySet()) { if (userIdMap.get(key) != null ) { webSocketSet.get(key).sendMessage(message); } } } } catch (IOException e) { e.printStackTrace(); } } /** * 发生错误时调用 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { System.out.println( "发生错误" ); error.printStackTrace(); } /** * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。 * * @param message * @throws IOException */ public void sendMessage(String message) throws IOException { this .session.getAsyncRemote().sendText(message); } /** * 查询连接人数的方法 * @return */ public static synchronized int getOnlineCount() { return onlineCount; } /** * 连接人数+1的方法 * @return */ public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } /** * 连接人数-1的方法 * @return */ public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } /** * 这个方法没用到,但是也先留着,说不定哪天就用到了 * 发送信息给指定ID用户,如果用户不在线则返回不在线信息给自己 * * @param message * @param sendUserId * @throws IOException */ public void sendtoUser(String message, List<String> sendUserId) throws IOException { for (String userId : sendUserId) { if (webSocketSet.get(userId) != null ) { webSocketSet.get(userId).sendMessage(message); } } } /** * rabbit订阅队列中的消息(发送消息) * * @param message */ @RabbitHandler public void process(String message) { onMessage(message, session); } } |
4.定义MsgSenderFanout类,往mq交换机上发送消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MsgSenderFanout { @Autowired private AmqpTemplate rabbitTemplate; /** * 在自己的方法中调用这个类下的这个方法,根据自己不同的需求 * 接收消息,并将消息传到mq交换机上,绑定了该交换机的队列的消费者都可收到该消息 * @param message */ public void send(String message) { System.out.println( "Sender : " + message); rabbitTemplate.convertAndSend( "myfanoutExchange" , "" , message); } } |
5.html页面
这个页面是针对websocket的页面,我用来测试用的,我自己的页面是用的layui,就不放出来了
如果想测试可以写一个测试类来调用第4步的send方法,传参为6,应该就会在页面上显示了
页面上还有一个心跳检测有待补全,后期会更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | <!DOCTYPE html> <html> <head> <title>Java后端WebSocket的Tomcat实现</title> <link rel= "stylesheet" href= "../../../static/layui2/css/layui.css" media= "all" /> <link rel= "stylesheet" href= "../../../static/css/font_eolqem241z66flxr.css" media= "all" /> <link rel= "stylesheet" href= "../../../static/css/user.css" media= "all" /> </head> <body> Welcome<br/><input id= "text" type= "text" /> <button onclick= "send()" >发送消息</button> <hr/> <button onclick= "closeWebSocket()" >关闭WebSocket连接</button> <hr/> <div id= "message" ></div> </body> <script type= "text/javascript" src= "../../../static/js/jquery.js" ></script> <script type= "text/javascript" src= "../../../static/layui2/layui.js" ></script> <script type= "text/javascript" src= "../../../static/js/queryUtils.js" ></script> <script type= "text/javascript" > var websocket = null ; //判断当前浏览器是否支持WebSocket if ( 'WebSocket' in window) { //这个地方特别坑,建立连接的地址在网上众说纷纭 //我个人实现的方法是 //ws:如果你是http请求就写ws;如果你是https请求得改成wss //localhost:8080:项目启动地址(本地),如果登陆页面是:http://localhost:8080/login,就用localhsot:8080 //websocket:这个写上WebSocketServer类上@ServerEndpoint注解后的value值 //uid:这个是传到后台去的登陆id websocket = new WebSocket( "ws://localhost:8080/websocket?uid=6" ); } else { alert( '当前浏览器 Not support websocket' ) } //连接发生错误的回调方法 websocket.onerror = function () { setMessageInnerHTML( "WebSocket连接发生错误" ); }; //连接成功建立的回调方法 websocket.onopen = function () { setMessageInnerHTML( "WebSocket连接成功" ); } //接收到消息的回调方法 websocket.onmessage = function (res) { setMessageInnerHTML(res.data); } //连接关闭的回调方法 websocket.onclose = function () { setMessageInnerHTML( "WebSocket连接关闭" ); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () { closeWebSocket(); } //将消息显示在网页上 function setMessageInnerHTML(innerHTML) { document.getElementById( 'message' ).innerHTML += innerHTML + '<br/>' ; } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); } //发送消息,这是websocket的实时消息 function send() { var message = document.getElementById( 'text' ).value; websocket.send(message); } </script> </html> |
转自:https://blog.csdn.net/weixin_43738469/article/details/106197705?utm_medium=distribute.pc_relevant.none-task-blog-searchFromBaidu-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-searchFromBaidu-3.control
标签:
websocket
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具