springboot WebSocket的使用
1、前言
在springboot中开发websock相关功能,报Error during WebSocket handshake Unexpected response code 404
2、原因分析
发现是websocket绑定终结点错误,需要添加WebSocketConfig
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,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
还需ShiroConfig
权限通过
filterChainDefinitionMap.put("/ws/**", "anon");
3、补充websock和smartSocket结合使用
package com.ruoyi.project.front.socket; import org.smartboot.socket.MessageProcessor; import org.smartboot.socket.StateMachineEnum; import org.smartboot.socket.transport.AioQuickServer; import org.smartboot.socket.transport.AioSession; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutionException; @Component @ServerEndpoint("/ws/webSocket") public class AudioWebSocket implements MessageProcessor<String> { //███████████████ websocket ████████████████████████████████████████████████████████████████████████████████████ //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 /** * 在线数量 */ private static int onlineCount = 0; /** * 获取在线数量 */ public static synchronized int getOnlineCount() { return onlineCount; } /** * 增加在线数量 */ public static synchronized void addOnlineCount() { AudioWebSocket.onlineCount++; } /** * 减去在线数量 */ public static synchronized void subOnlineCount() { AudioWebSocket.onlineCount--; } /** * websocket集合 */ //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 private static CopyOnWriteArraySet<AudioWebSocket> webSocketSet = new CopyOnWriteArraySet<>(); /** * webSocketSession */ //与某个客户端的连接会话,需要通过它来给客户端发送数据 websocket private Session webSocketSession; @OnOpen public void onOpen(Session session) throws IOException, ExecutionException, InterruptedException { System.out.println("websocket connect"); this.webSocketSession = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 } @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 } @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } @OnMessage public void onMessage(String message, Session session) { System.out.println("来自客户端的消息:" + message); // if (message.startsWith("CC")) { // Timer timer = new Timer(); // timer.schedule(new TimerTask() { // @Override // public void run() { // sendMessageToAllClient(SimpleDateFormat.getDateInstance().format(new Date()) + "222"); // } // }, 0, 1000); // } } public synchronized void sendMessageToAllClient(String message) { for (AudioWebSocket item : webSocketSet) { try { item.webSocketSession.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); continue; } } } //███████████████ AioSession ████████████████████████████████████████████████████████████████████████████████████ public static void aioStart() { try { aioQuickServer = new AioQuickServer<>(12000, new SinglechipFormProtocol(), new AudioWebSocket()); aioQuickServer.setReadBufferSize(2048); aioQuickServer.start(); System.out.println("通信模块 服务启动 "); } catch (Exception e) { e.printStackTrace(); // LogHelper.info("通信模块 socket服务关闭 "); aioQuickServer.shutdown(); } } public static void defend() { new Thread(() -> { aioStart(); }).start(); } /** * 服务端变量 */ public static AioQuickServer<String> aioQuickServer; //与某个客户端的连接会话,需要通过它来给客户端发送数据 /** * aioSession */ private static AioSession<String> aioSession; @Override public void process(AioSession<String> aioSession, String s) { System.out.println("aioSession " + s); sendMessageToAllClient(s); } @Override public void stateEvent(AioSession<String> aioSession, StateMachineEnum stateMachineEnum, Throwable throwable) { switch (stateMachineEnum) { case NEW_SESSION: this.aioSession = aioSession; System.out.println("通信模块 NEW_SESSION state:" + stateMachineEnum); break; case SESSION_CLOSED: System.out.println("通信模块 SESSION_CLOSED state:" + stateMachineEnum); this.aioSession = null; break; default: System.out.println("通信模块 other state:" + stateMachineEnum + " " + throwable.toString()); break; } } }
SinglechipFormProtocol
package com.ruoyi.project.front.socket; import org.smartboot.socket.Protocol; import org.smartboot.socket.transport.AioSession; import java.nio.ByteBuffer; public class SinglechipFormProtocol implements Protocol<String> { private static final int INT_LENGTH = 4; @Override public String decode(ByteBuffer readBuffer, AioSession<String> session, boolean bool) { // TODO Auto-generated method stub int length = readBuffer.limit(); if (readBuffer.remaining() < INT_LENGTH) { return null; } byte[] bytes = new byte[length]; readBuffer.get(bytes, 0, length); String retStr = new String(bytes); // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); // retStr = sdf.format(new Date())+ ","+ retStr; return retStr; } @Override public ByteBuffer encode(String str, AioSession<String> session) { // TODO Auto-generated method stub byte[] dd = str.getBytes(); ByteBuffer b = ByteBuffer.allocate(INT_LENGTH); b.put(dd); b.flip(); return b; } }
pom.xml
<dependency> <groupId>org.smartboot.socket</groupId> <artifactId>aio-core</artifactId> <version>1.3.12</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
分类:
Tools
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2018-12-28 c# 图片资料