SpringBoot2.0集成WebSocket,多客户端
适用于单客户端,一个账号登陆一个客户端,登陆多个客户端会报错
The remote endpoint was in state [TEXT_FULL_WRITING]
这是因为此时的session是不同的,只能锁住一个session,解决此问题的方法把全局静态对象锁住,因为账号是唯一的
/*** @Description 开启springboot对websocket的支持* @Author WangKun* @Date 2023/8/14 17:21* @Version*/
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
@Configuration
public class WebSocketConfig{/*** @Description 注入一个ServerEndpointExporter, 会自动注册使用@ServerEndpoint注解* @param* @Throws* @Return org.springframework.web.socket.server.standard.ServerEndpointExporter* @Date 2023-08-14 17:26:31* @Author WangKun*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
/*** @Description websocket服务,不考虑分组* @Author WangKun* @Date 2023/8/14 17:29* @Version*/
@ConditionalOnClass(value = WebSocketConfig.class)
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocket {private static final long SESSION_TIMEOUT = 60000;//存放每个客户端对应的WebSocket对象。private static final ConcurrentHashMap<String, CopyOnWriteArraySet<WebSocket>> WEB_SOCKET_MAP = new ConcurrentHashMap<>();//与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;private String userId;/*** @param o* @Description 重写防止session重复* @Throws* @Return boolean* @Date 2023-09-01 10:02:51* @Author WangKun*/@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()) {return false;}WebSocket that = (WebSocket) o;return Objects.equals(session, that.session);}@Overridepublic int hashCode() {return Objects.hash(session);}/*** @param session* @param userId* @Description 建立连接* @Throws* @Return void* @Date 2023-08-14 17:52:08* @Author WangKun*/@SneakyThrows@OnOpenpublic void onOpen(final Session session, @PathParam("userId") String userId) {this.session = session;this.userId = userId;session.setMaxIdleTimeout(SESSION_TIMEOUT);//先查找是否有uniCodeCopyOnWriteArraySet<WebSocket> users = WEB_SOCKET_MAP.get(userId);if (users == null) {//处理多个同时连接并发synchronized (WEB_SOCKET_MAP) {if (!WEB_SOCKET_MAP.contains(userId)) {users = new CopyOnWriteArraySet<>();WEB_SOCKET_MAP.put(userId, users);}}}users.add(this);sendMessage(String.valueOf(ResponseCode.CONNECT_SUCCESS.getCode()));log.info("用户--->{} 连接成功,当前在线人数为--->{}", userId, WEB_SOCKET_MAP.size());}/*** @param message* @Description 向客户端发送消息 session.getBasicRemote()与session.getAsyncRemote()的区别* @Throws* @Return void* @Date 2023-08-14 17:51:07* @Author WangKun*/@SneakyThrowspublic void sendMessage(String message) {// 加锁避免阻塞// 如果有多个客户端的话,亦或者同一个用户,或者打开了多个浏览器(同一个用户打开多个客户端或者多个界面),开了多个页面,此时Session是不同的,只能锁住一个session,所以锁住全局静态对象
// synchronized(session) {
// this.session.getBasicRemote().sendText(message);
// }synchronized (WEB_SOCKET_MAP) {CopyOnWriteArraySet<WebSocket> users = WEB_SOCKET_MAP.get(userId);if (users != null) {for (WebSocket user : users) {// 判断当前客户端的用户是否打开连接if (user.session.isOpen()) {user.session.getBasicRemote().sendText(message);log.info("向客户端发送数据--->{} 数据为--->{}", userId, message);}}}}}/*** @param* @Description 关闭连接* @Throws* @Return void* @Date 2023-08-14 17:52:30* @Author WangKun*/@SneakyThrows@OnClosepublic void onClose(Session session) {// 避免多人同时在线直接关闭通道。CopyOnWriteArraySet<WebSocket> copyOnWriteArraySet = WEB_SOCKET_MAP.get(this.userId);if (!copyOnWriteArraySet.isEmpty()) {Object[] objects = copyOnWriteArraySet.toArray();for (Object object : objects) {if (((WebSocket) object).session.equals(session)) {//删除当前用户WEB_SOCKET_MAP.get(this.userId).remove((WebSocket) object);// 如果有一个客户端登陆 下线清除用户if (WEB_SOCKET_MAP.get(this.userId).isEmpty()) {WEB_SOCKET_MAP.remove(this.userId);}CloseReason close = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "关闭客户端,下线!");session.close(close);log.info("用户--->{} 关闭连接!", userId);}}}}/*** @param message* @param session* @Description 收到客户端消息* @Throws* @Return void* @Date 2023-08-15 10:54:55* @Author WangKun*/@SneakyThrows@OnMessagepublic void onMessage(String message, Session session) {//枷锁避免多个资源互抢//这一块可以操作数据,比如存到数据// 同一个用户,多个地方登录(多个session),循环发送消息,// 如果有多个客户端的话,亦或者同一个用户,或者打开了多个浏览器,开了多个页面,此时Session是不同的,只能锁住一个session,所以锁住全局静态对象synchronized (WEB_SOCKET_MAP) {CopyOnWriteArraySet<WebSocket> users = WEB_SOCKET_MAP.get(userId);if (users != null) {for (WebSocket user : users) {if (user.session.isOpen()) {user.session.getBasicRemote().sendText("pong");log.info("收到客户端发送的心跳的数据--->{} 数据为--->{}", userId, message);}}}}}/*** @param session* @param error* @Description 发生错误时* @Throws* @Return void* @Date 2023-08-15 10:55:27* @Author WangKun*/@SneakyThrows@OnErrorpublic void onError(Session session, Throwable error) {CopyOnWriteArraySet<WebSocket> users = WEB_SOCKET_MAP.get(userId);if (!users.isEmpty()) {Object[] objects = users.toArray();for (Object object : objects) {if (((WebSocket) object).session.equals(session)) {//删除当前用户WEB_SOCKET_MAP.get(this.userId).remove((WebSocket) object);// 如果有一个客户端登陆 下线清除用户if (WEB_SOCKET_MAP.get(this.userId).isEmpty()) {WEB_SOCKET_MAP.remove(this.userId);}CloseReason close = new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "异常,下线!");session.close(close);log.error("用户--->{} 错误!" + userId, "原因--->{}" + error.getMessage(), error);}}}
// WEB_SOCKET_MAP.remove(userId);
// log.error("用户--->{} 错误!" + userId, "原因--->{}" + error.getMessage(), error);}/*** @param userId* @param message* @Description 通过userId向客户端发送消息(指定用户发送)* @Throws* @Return void* @Date 2023-08-14 18:01:35* @Author WangKun*/public static void sendTextMessageByUserId(String userId, String message) {CopyOnWriteArraySet<WebSocket> users = WEB_SOCKET_MAP.get(userId);if (users != null) {for (WebSocket user : users) {user.sendMessage(message);log.info("服务端发送消息到用户{},消息:{}", userId, message);}}}/*** @param message* @Description 群发自定义消息* @Throws* @Return void* @Date 2023-08-14 18:03:38* @Author WangKun*/public static void sendTextMessage(String message) {// 如果在线一个就广播if (!WEB_SOCKET_MAP.isEmpty()) {for (String item : WEB_SOCKET_MAP.keySet()) {CopyOnWriteArraySet<WebSocket> users = WEB_SOCKET_MAP.get(item);if (users != null) {for (WebSocket user : users) {user.sendMessage(message);log.info("服务端发送消息到用户{},消息:{}", item, message);}}}}}
}
相关文章:
SpringBoot2.0集成WebSocket,多客户端
适用于单客户端,一个账号登陆一个客户端,登陆多个客户端会报错 The remote endpoint was in state [TEXT_FULL_WRITING] 这是因为此时的session是不同的,只能锁住一个session,解决此问题的方法把全局静态对象锁住,因…...
华为OD机试 - 等和子数组最小和 - 深度优先搜索(Java 2022 Q4 100分)
目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷B卷)》…...
浏览器会因为什么样的脚本而崩溃
浏览器可能因为以下几种情况而崩溃: 无限循环:如果JavaScript脚本包含一个无限循环,浏览器将无法停止脚本的执行,导致浏览器不响应甚至崩溃。例如,以下代码会导致无限循环: while (true) {// 无限循环 } 内…...
生成与调用C++动态链接库(so文件)
文章目录 前言生成C动态链接库步骤1:编写C源码步骤2:生成共享库步骤3:验证生成的SO文件 调用C动态链接库步骤1:修改原来makefile步骤2:编译调用程序步骤3:运行调用程序 总结 前言 动态链接库是代码重用和模…...
韶音的耳机怎么样,韶音骨传导耳机值得入手吗
韶音关于骨传导耳机的产品在质量方面还是有着不错的表现,其最具代表性的骨传导耳机就是韶音OpenRun Pro,在国产骨传导耳机中是具备了一定的知名度,有着自主研发的声学技术。 最突出的点就在于颜色上多样化,有着经典的黑色…...
STM32G030F6 (SOP-20)Cortex ® -M0+, 32KB Flash, 8KB RAM, 17 GPIOs
淘宝淘了一批 STM32G030F6P6 SOP20.先备注一下, 还没想到能干嘛用. 手上的 STM32F103C6T6还剩一些. 一堆 “淘宝原厂STM32F103C8T6”, 还烫着手. 理解信息: ( 逐步补充 ) System Clock GPIOs GPIOs 17 PA[7:0] : 8bits USART Timer ADC I2…...
常用的字符集和字符编码
基础概念 字符 字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等 字符集 一个操作系统支持的字符的集合。 字符编码和解码 将每个字符都设置一个唯一编号,编码就是将字符集中的字符编号以一定形式转化为字节存储下来,…...
容器技术简介
引言 随着云计算、大数据、人工智能等技术的不断发展,容器技术作为一种新兴的虚拟化技术,正逐渐成为IT领域的热点。容器技术可以帮助开发者更好地管理、部署和扩展应用程序,提高开发效率和应用程序的可靠性。本文将深入探讨容器技术的概念、…...
数据分享|R语言用lme4多层次(混合效应)广义线性模型(GLM),逻辑回归分析教育留级调查数据...
全文链接:http://tecdat.cn/?p22813 本教程为读者提供了使用频率学派的广义线性模型(GLM)的基本介绍。具体来说,本教程重点介绍逻辑回归在二元结果和计数/比例结果情况下的使用,以及模型评估的方法(点击文末“阅读原文…...
macos 不支持svn安装
macos 10.13可能不支持svn命令,所以要安装 xcode-select --install 弹窗在线安装失败的话只能手动下载安装 打开:Sign In - Apple 搜索Command Line Tools (macOS 10.13) 下载9.4.1版本直接安装后即可...
如何通过实际操作来加深对Linux命令和概念的理解?
作为一个新手,你一定不要被Linux那堆命令吓到。其实,它们就像你的“超能力”,只要你掌握它们,你就能成为Linux世界的超级英雄! 首先,我们要了解的是,Linux命令其实就像你的“魔法咒语”&#x…...
【开发语言】C语言与Python的互操作详解
博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客…...
华为配置聚合vlan(Super vlan--Sub vlan)
聚合vlan,Aggregation vlan,也称Super vlan,可以实现用Sub vlan二层隔离广播域,但又将这些Sub vlan聚合使用同一IP子网和网关的情况。 这样,多个Sub-VLAN共享一个网关地址,节约了子网号、子网定向广播地址、…...
CentOS7安装时直接跳过了安装信息摘要页面的解决方法
最近在配置Hadoop虚拟机的时候,创建的centos7虚拟机在安装信息摘要时直接自动跳过,直接跳到设置用户名和密码,在重复多次的重新删除安装后发现了问题所在: 在进行到选择操作系统来源时,注意是否出现“该操作系统将使用…...
python基础运用例子
python基础运用例子 1、⼀⾏代码交换 a , b :a, b b, a2、⼀⾏代码反转列表 l[::-1]3、合并两个字典 res {**dict1, **dict2}**操作符合并两个字典for循环合并dict(a, **b) 的方式dict(a.items() b.items()) 的方式dict.update(other_dict) 的方式 4、⼀⾏代码列…...
k8s基本概念
一、什么是Kubernetes二:Kubernetes部署方式的演变三、为什么要用K8S四、K8S的特性五、Kubernetes 集群架构与组件5.1 Master 组件① Kube-apiserver② Kube-controller-manager③ Kube-scheduler④ AUTH 认证模块 5.2 配置存储中心5.3 Node 组件① Kubelet② Kube-…...
Python exp() 函数
描述 exp() 方法返回x的指数,ex。 语法 以下是 exp() 方法的语法: import mathmath.exp( x ) 注意:exp()是不能直接访问的,需要导入 math 模块,通过静态对象调用该方法。 参数 x -- 数值表达式。 返回值 返回x的指数,ex。 实例 以下展…...
Day 34 贪心算法 part03 : 1005.K次取反后最大化的数组和 134. 加油站 135. 分发糖果
134. 加油站 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas…...
气象站的构成及功能应用
气象站是一种用于观测、记录和报告天气数据的设备。它是由数据采集系统、通讯系统、供电系统和立杆支架构成。 一、气象站的构成: 数据采集系统:用于测量气温、湿度、风速、风向、气压、降雨量、雪深等气象参数。 通讯系统:收集和处理传感…...
Qt中布局管理使用总结
目录 1. 五大布局 1.1 QVBoxLayout垂直布局 1.2 QHBoxLayout水平布局 1.3 QGridLayout网格布局 1.4 QFormLayout表单布局 1.5 QStackedLayout分组布局 1.6 五大布局综合应用 2. 分割窗口 3. 滚动区域 4. 停靠区域 1. 五大布局 1.1 QVBoxLayout垂直布局 #include <…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...
鸿蒙HarmonyOS 5军旗小游戏实现指南
1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发,采用DevEco Studio实现,包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...
