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>
posted @   一只桔子2233  阅读(628)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2018-12-28 c# 图片资料
点击右上角即可分享
微信分享提示