websocket前后端长连接之java部分

一共有4个类,第一个WebSocketConfig 配置类

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate WebSocketHandler webSocketHandler;@Autowiredprivate WebSocketInterceptor webSocketInterceptor;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(webSocketHandler, "/ws").addInterceptors(webSocketInterceptor).setAllowedOrigins("*");}
}

第二个,拦截器,这里我区分了pc和app,因为代码需求是同一个id登录的用户要在pc端和app端同时连接websocket,为做区分,在pc的userid后面加了pc两个字母.

@Component
public class WebSocketInterceptor implements HandshakeInterceptor {private final Logger logger = LoggerFactory.getLogger(WebSocketInterceptor.class);@Resourceprivate ISysUserService userService;/*** 握手前* @param request    请求对象* @param response   响应对象* @param wsHandler  请求处理器* @param attributes 属性域* @return true放行,false拒绝* @throws Exception 可能抛出的异常*/@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, org.springframework.web.socket.WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {// 获得请求参数Map<String, String> paramMap = HttpUtil.decodeParamMap(request.getURI().getQuery(), Charset.defaultCharset());String userId = paramMap.get("userId");if (CharSequenceUtil.isNotBlank(userId)) {if (userId.endsWith("pc")){
//                String substring = userId.substring(0, userId.length() - 2);
//                // 校验连接人在系统是否存在
//                SysUser user = userService.selectUserById(Long.valueOf(substring));
//                if (user == null) {
//                    response.setStatusCode(HttpStatus.UNAUTHORIZED);
//                    return false;
//                }}else {// 校验连接人在系统是否存在SysUser user = userService.selectUserById(Long.valueOf(userId));if (user == null) {response.setStatusCode(HttpStatus.UNAUTHORIZED);return false;}}// 放入属性域attributes.put("userId", userId);logger.info("用户:{}握手成功!", userId);return true;} else {logger.info("接受到一个websocket连接请求但是没有参数!");}return false;}/*** 握手后** @param request   请求独享* @param response  响应对象* @param wsHandler 处理器* @param exception 抛出的异常*/@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, org.springframework.web.socket.WebSocketHandler wsHandler, Exception exception) {logger.info("握手结束!");}
}

第三个是管理器,其中的add方法,本身是有一个判重机制,如果该连接已存在就把原来的踢下线,重新连接新的,防止出现多个同样的id的问题.但是这又导致了新的频繁关闭重连的问题,所以后来改成了如果已经存在就直接return

@Slf4j
public class WsSessionManager {private WsSessionManager() {}private static final Logger logger = LoggerFactory.getLogger(WsSessionManager.class);/*** 记录当前在线连接数*/private static AtomicInteger onlineCount = new AtomicInteger(0);/*** 保存连接 session 的地方*/private static final ConcurrentHashMap<String, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>(99999);/*** 添加 session** @param key     键* @param session 值*/public static synchronized void add(String key, WebSocketSession session) {WebSocketSession existingSession = SESSION_POOL.get(key);if (existingSession != null) {if (existingSession.equals(session)) {logger.info("用户 {} 的 WebSocket 已存在,无需重复添加", key);return;}
//            if (existingSession.isOpen()) {
//                try {
//                    existingSession.close();
//                    logger.info("关闭旧的连接, userId: {}", key);
//                } catch (IOException e) {
//                    logger.error("关闭旧的连接时出现异常, userId: {}, 异常: {}", key, e.getMessage());
//                }
//            }if (existingSession.isOpen()) return;}SESSION_POOL.put(key, session);onlineCount.incrementAndGet();logger.info("新连接已添加, userId: {}, 当前在线人数: {}", key, getOnlineCount());}/*** 删除 session, 会返回删除的 session** @param key 键* @return 值*/public static synchronized WebSocketSession remove(String key) {WebSocketSession session = SESSION_POOL.remove(key);if (session != null) {onlineCount.decrementAndGet();logger.info("连接已移除, userId: {}, 当前在线人数: {}", key, getOnlineCount());}return session;}/*** 删除并同步关闭连接** @param key 键*/public static synchronized void removeAndClose(String key) {WebSocketSession session = remove(key);if (session != null) {try {session.close();logger.warn("关闭WebSocket会话, userId: {}", key);} catch (IOException e) {logger.error("关闭会话时出现异常, userId: {}, 异常: {}, {}", key, e.getMessage(), e);}}}/*** 获得 session** @param key 键* @return 值*/public static WebSocketSession get(String key) {return SESSION_POOL.get(key);}/*** 获取当前在线连接数** @return 在线连接数*/public static int getOnlineCount() {return onlineCount.get();}/*** 获得 Map** @return 值*/public static ConcurrentMap<String, WebSocketSession> getMap() {return SESSION_POOL;}
}

第四个是真正发送消息的处理器

@Component
public class WebSocketHandler extends TextWebSocketHandler {private final Logger logger = LoggerFactory.getLogger(WebSocketHandler.class);private static final String KEY = "userId";/*** socket 建立成功事件* @param session session对象*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) {Object userId = session.getAttributes().get(KEY);if (userId != null) {// 将用户的连接放入 WsSessionManager,会自动关闭之前的旧连接WsSessionManager.add(userId.toString(), session);logger.info("用户连接成功, userId: {}", userId);} else {logger.warn("未能在连接中找到 userId 属性");}logger.info("建立连接了, 当前在线人数: {}, session: {}, 当前map: {}", WsSessionManager.getOnlineCount(), session, WsSessionManager.getMap());}/*** 接收消息事件** @param session session对象* @param message 接收到的消息* @throws Exception 可能抛出的异常*/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 获得客户端传来的消息
//        String payload = message.getPayload();logger.info("收到ws消息: {}", message);// 返回一条确认消息给发消息的用户TextMessage responseMessage = new TextMessage("pong");session.sendMessage(responseMessage);}/*** socket 断开连接时** @param session session对象* @param status  断开状态* @throws Exception 可能抛出的异常*/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {logger.info("断开连接了,session为{}", session == null ? "" : session);Object token = session.getAttributes().get(KEY);if (token != null) {// 用户退出,移除缓存WsSessionManager.removeAndClose(token.toString());}}/*** 发送消息给指定设备** @param serialNumber 序列号* @param message      消息内容* @param type         1跳通知 2跳客户 3手机打电话 前端用  8 pc用未读消息数量   9 当前app是否在线,true或者false* @param noticeId     通知id,已读用*/public void sendMessage(String serialNumber, String message, Integer type, Long noticeId) {WebSocketSession webSocketSession = WsSessionManager.get(serialNumber);try {if (webSocketSession != null && webSocketSession.isOpen()) {JSONObject jsonObject;jsonObject = JSONObject.of("type", type, "value", message, "noticeId", noticeId);webSocketSession.sendMessage(new TextMessage(jsonObject.toString()));logger.info("发送消息给{},消息内容为{}", serialNumber, message);}} catch (Exception e) {logger.error("消息发送失败,设备{},失败原因{}{}", webSocketSession.getAttributes().get(KEY), e.getMessage(), e);}}/*** 发送消息给指定设备** @param serialNumber 序列号* @param message      消息内容* @param type         1跳通知 2跳客户 3手机打电话 前端用  8 pc用未读消息数量   9 当前app是否在线,true或者false* @param notice     通知整个对象*/public void sendMessage(String serialNumber, String message, Integer type, ClientNoticeDO notice, Integer other) {WebSocketSession webSocketSession = WsSessionManager.get(serialNumber);try {if (webSocketSession != null && webSocketSession.isOpen()) {JSONObject jsonObject = JSONObject.of("type", type, "value", message, "notice", notice);webSocketSession.sendMessage(new TextMessage(jsonObject.toString()));logger.info("发送消息给{},消息内容为{}", serialNumber, message);} else {logger.warn("WebSocket 会话不可用, userId: {}", serialNumber);}} catch (IOException e) {logger.error("WebSocket 消息发送失败, userId: {}, 原因: {}", serialNumber, e.getMessage(), e);WsSessionManager.remove(serialNumber); // 自动移除无效会话} catch (Exception e) {logger.error("消息发送时发生未知错误, userId: {}, 原因: {}", serialNumber, e.getMessage(), e);}}/*** 广播消息** @param message 消息*/public void sendMessageAll(String message) {WsSessionManager.getMap().keySet().forEach(e -> sendMessage(e, message, 2, (Long) null));}
}

其中的sendMessage方法根据自己的业务需求有一个重载方法,正常一个sendMessage就足够了.日志相关的酌情增减.

心跳:在handleTextMessage方法中,接收到前端任何消息都返回一个pong,前端如果一段时间未收到pong就会发起重连,以此保证连接不中断.如果业务有前端发来的其他消息则加个if判断即可.

最终使用的时候注入

@Autowired
private WebSocketHandler webSocketHandler;//然后调用webSocketHandler.sendMessage(XXX,XXX,XXX)//即可.

连接的地址:ws://IP:端口/?userId=1
其中/ws是在WebSocketConfig配置的,
userId是在WebSocketHandler配置的KEY

最后附上在线连接websocket测试的网站:http://www.websocket-test.com/
以及相关可以直接测试的idea插件:CoolRequest
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/480524.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyCharm中Python项目打包并运行到服务器的简明指南

目录 一、准备工作 二、创建并设置Python项目 创建新项目 配置项目依赖 安装PyInstaller 三、打包项目 打包为可执行文件 另一种打包方式&#xff08;使用setup.py&#xff09; 四、配置服务器环境 五、上传可执行文件到服务器 六、在服务器上运行项目 配置SSH解释…

【UE5 C++课程系列笔记】05——组件和碰撞

效果 可以看到我们可以实现的功能是 &#xff08;1&#xff09;可以通过鼠标旋转视角 &#xff08;2&#xff09;通过使用Pawn移动组件来控制Pawn移动 &#xff08;3&#xff09;Pawn碰到物体会被阻挡然后逐渐滑动 &#xff08;4&#xff09;通过空格切换激活/关闭粒子效果…

格网法计算平面点云面积(matlab版本)

1、原理介绍 格网法计算平面点云面积&#xff0c;其思想类似高中油膜法计算面积。其将点云投影到水平面&#xff0c;再将点云划分成尺寸相同的格网。最后&#xff0c;统计格网内包含点的数量number&#xff0c;那么可利用如下公式计算得到点云的面积&#xff1a; Aeranumber*L…

ZooKeeper 基础知识总结

先赞后看&#xff0c;Java进阶一大半 ZooKeeper 官网这样介绍道&#xff1a;ZooKeeper 是一种集中式服务&#xff0c;用于维护配置信息、命名、提供分布式同步和提供组服务。 各位hao&#xff0c;我是南哥&#xff0c;相信对你通关面试、拿下Offer有所帮助。 ⭐⭐⭐一份南哥编写…

2024年第十三届”认证杯“数学中国数学建模国际赛(小美赛)

↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

ATTCK红队评估实战靶场(二)

http://vulnstack.qiyuanxuetang.net/vuln/?page2 描述&#xff1a;红队实战系列&#xff0c;主要以真实企业环境为实例搭建一系列靶场&#xff0c;通过练习、视频教程、博客三位一体学习。本次红队环境主要Access Token利用、WMI利用、域漏洞利用SMB relay&#xff0c;EWS re…

如何启用本机GPU硬件加速猿大师播放器网页同时播放多路RTSP H.265 1080P高清摄像头RTSP视频流?

目前市面上主流播放RTSP视频流的方式是用服务器转码方案&#xff0c;这种方案的好处是兼容性更强&#xff0c;可以用于不同的平台&#xff0c;比如&#xff1a;Windows、Linux或者手机端&#xff0c;但是缺点也很明显&#xff1a;延迟高、播放高清或者同时播放多路视频视频容易…

rocylinux9.4安装prometheus监控

一.上传软件包 具体的软件包如下&#xff0c;其中kubernetes-mixin是下载的监控kubernetes的一些监控规则、dashbaordd等。 二.Prometheus配置 1.promethes软件安装 #解压上传后的软件包 [rootlocalhost ] cd /opt [rootlocalhost opt]# tar xf prometheus-2.35.3.linux-amd…

第五课 Unity资源导入工作流效率优化(AssetGraph工具)

上期我们学习了简单的animation动画的优化&#xff0c;接下来我们继续资源导入效率的优化 工程目录 首先我们来学习一下工程目录结构及用途 Asset文件夹&#xff1a;用来储存和重用的项目资产 Library文件夹&#xff1a;用来储存项目内部资产数据信息的目录 Packages文件夹…

Docker pull镜像拉取失败

因为一些原因&#xff0c;很多镜像仓库拉取镜像失败&#xff0c;所以需要更换不同的镜像&#xff0c;这是2024/11/25测试可用的仓库。 标题1、 更换镜像仓库的地址&#xff0c;编辑daemon.json文件 vi /etc/docker/daemon.json标题2、然后将下面的镜像源放进去或替换掉都可以…

天锐绿盾加密软件与Ping32联合打造企业级安全保护系统,确保敏感数据防泄密与加密管理

随着信息技术的飞速发展&#xff0c;企业在日常经营过程中产生和处理的大量敏感数据&#xff0c;面临着越来越复杂的安全威胁。尤其是在金融、医疗、法律等领域&#xff0c;数据泄漏不仅会造成企业巨大的经济损失&#xff0c;还可能破坏企业的信誉和客户信任。因此&#xff0c;…

人工智能-深度学习-Torch框架-手动构建回归流程

from sklearn.datasets import make_regression import math import random import torch from sklearn.datasets import make_regression: 导入make_regression函数&#xff0c;用于生成回归数据集。 import math: 导入math模块&#xff0c;用于进行数学计算&#xff0c;例如…

java全栈day10--后端Web基础(基础知识)之续集

一、Servlet执行流程 二、Http协议&#xff08;相对Tomcat和servlet重要一点&#xff09; 2.1Http-概叙 2.2Http-请求协议 2.2.3请求数据格式 2.2.3请求数据获取 先启动服务器 访问/hello Servlet 访问浏览器端Http协议数据 查看数据

web安全之信息收集

在信息收集中,最主要是就是收集服务器的配置信息和网站的敏感信息,其中包括域名及子域名信息,目标网站系统,CMS指纹,目标网站真实IP,开放端口等。换句话说,只要是与目标网站相关的信息,我们都应该去尽量搜集。 1.1收集域名信息 知道目标的域名之后,获取域名的注册信…

基于YOLOv8深度学习的智慧农业棉花采摘状态检测与语音提醒系统(PyQt5界面+数据集+训练代码)

智慧农业在现代农业中的应用日益广泛&#xff0c;其核心目标是通过智能化手段实现农业生产的自动化、精准化和高效化&#xff0c;而精准采摘技术作为智慧农业的重要组成部分&#xff0c;正受到越来越多的关注。棉花作为一种经济作物&#xff0c;其采摘过程传统上依赖于人工劳作…

使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost)

使用vcpkg自动链接tinyxml2时莫名链接其他库&#xff08;例如boost&#xff09; vcpkg的自动链接功能非常方便&#xff0c;但在某些情况下会出现过度链接的问题。 链接错误症状 以tinyxml2为例&#xff0c;程序中调用tinyxml2的函数后&#xff0c;若vcpkg中同时存在opencv和…

gitlab自动打包python项目

现在新版的gitlab可以不用自己配置runner什么的了 直接写.gitlab-ci.yml文件就行&#xff0c;这里给出一个简单的依靠setup把python项目打包成whl文件的方法 首先写.gitlab-ci.yml文件&#xff0c;放到项目根目录里 stages: # List of stages for jobs, and their or…

蓝桥杯每日真题 - 第24天

题目&#xff1a;&#xff08;货物摆放&#xff09; 题目描述&#xff08;12届 C&C B组D题&#xff09; 解题思路&#xff1a; 这道题的核心是求因数以及枚举验证。具体步骤如下&#xff1a; 因数分解&#xff1a; 通过逐一尝试小于等于的数&#xff0c;找到 n 的所有因数…

YOLOv10改进,YOLOv10添加TransNeXt中的ConvolutionalGLU模块,CVPR2024,二次创新C2f结构

摘要 由于残差连接中的深度退化效应,许多依赖堆叠层进行信息交换的高效视觉Transformer模型往往无法形成足够的信息混合,导致视觉感知不自然。为了解决这个问题,作者提出了一种聚合注意力(Aggregated Attention),这是一种基于仿生设计的token混合器,模拟了生物的中央凹…

(微信小程序)基于Spring Boot的校园失物招领平台的设计与实现(vue3+uniapp+mysql)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…