Springboot-websocket实现及底层原理

引入依赖

Spring Boot 中的 WebSocket 依赖于 Spring WebFlux 模块,使用了 Reactor Netty 库来实现底层的 WebSocket 通信。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>        

服务端配置

/*** WebSocket配置类*/
@Configuration
public class WebSocketConfig {/*** 注入ServerEndpointExporter的bean对象,自动注册使用了@ServerEndpoint注解的bean* @return*/@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}}

ServerEndpointExporter

        在Spring框架中,ServerEndpointExporter的注入是WebSocket配置的重要部分。

为什么要注入ServerEndpointExporter对象?

  1. 自动注册WebSocket端点: ServerEndpointExporter会扫描Spring应用上下文中使用了@ServerEndpoint注解的类,并自动注册这些类为WebSocket端点。这样,就不需要手动注册每个端点,简化了WebSocket端点的配置过程。

  2. 集成Spring框架: 通过ServerEndpointExporter,Spring框架能够更好地管理和配置WebSocket端点。例如,Spring的依赖注入功能可以用于WebSocket端点,使得端点可以依赖Spring管理的bean。

如果实现自动注册端点(函数registerEndpoints)?

请看下述注释:

  1. 创建一个linkedhashset集合来存储端点类.
  2. 从spring上下文中查找所有带有serverEndPoint的bean并添加到endPointClasses集合中
  3. 遍历集合,注册每一个端点类
  4. 从spring上下文中查找所有带有serverEndPointConfig的bean,放置在集合中并注册
protected void registerEndpoints() {// 创建一个LinkedHashSet来存储端点类,确保顺序和唯一性Set<Class<?>> endpointClasses = new LinkedHashSet<>();// 如果annotatedEndpointClasses不为空,将其全部添加到endpointClasses中if (this.annotatedEndpointClasses != null) {endpointClasses.addAll(this.annotatedEndpointClasses);}// 获取应用上下文ApplicationContext context = this.getApplicationContext();// 如果上下文不为空,从上下文中获取所有带有ServerEndpoint注解的bean的名字if (context != null) {String[] endpointBeanNames = context.getBeanNamesForAnnotation(ServerEndpoint.class);// 遍历所有bean名字for (String beanName : endpointBeanNames) {// 将bean对应的类型添加到endpointClasses中endpointClasses.add(context.getType(beanName));}}// 遍历所有收集到的端点类for (Class<?> endpointClass : endpointClasses) {// 注册每个端点类this.registerEndpoint(endpointClass);}// 如果上下文不为空,从上下文中获取所有ServerEndpointConfig类型的beanif (context != null) {Map<String, ServerEndpointConfig> endpointConfigMap = context.getBeansOfType(ServerEndpointConfig.class);// 遍历所有ServerEndpointConfig类型的beanfor (ServerEndpointConfig endpointConfig : endpointConfigMap.values()) {// 注册每个ServerEndpointConfigthis.registerEndpoint(endpointConfig);}}
}

创建websocket对象

/*** 服务端WebSocket对象*/
@ServerEndpoint(value = "/chat/{param}")
@Component
public class ChatEndpoint {//用来存储每一个客户端对象对应的ChatEndpoint对象  ConcurrentHashMap效率高,线程安全,但是key和value都不能为null
//    public static Map<String, ChatEndpoint> onlineUsers = new ConcurrentHashMap<>();public static CopyOnWriteArraySet<ChatEndpoint> webSocketCopyOnWriteArraySet = new CopyOnWriteArraySet<>();private String loginId;//声明Session对象,通过该对象可以发送消息给指定的用户private Session session;/*** 连接建立时被调用* @param session* @param param*/@OnOpenpublic void onOpen(Session session, @PathParam("param")String param){//将局部的session对象赋值给成员sessionthis.session = session;this.loginId = param; //1_1 0是企业,1是求职者//将当前登录用户存储到容器中webSocketCopyOnWriteArraySet.add(this);}/*** 接收到客户端发来的消息时被调用* @param message*/@OnMessagepublic void onMessage(String message){try{JSONObject messageObject = JSON.parseObject(message);Integer userId = messageObject.getInteger("userId");Integer enterpriseId = messageObject.getInteger("enterpriseId");Integer status = messageObject.getInteger("status");Integer chatId = messageObject.getInteger("chatId");String receiver ="";if(status==0){receiver ="USER_"+userId;}else{receiver ="ENTERPRISE_"+enterpriseId;}// 单聊for(ChatEndpoint chatEndpoint : webSocketCopyOnWriteArraySet){if(chatEndpoint.loginId.equals(receiver)){chatEndpoint.session.getBasicRemote().sendText(JSONObject.toJSONString(messageObject));}}}catch(Exception e){e.printStackTrace();}}/*** 发送系统消息* @param receiverId*/public void sendSystemMessage(String receiverId) {try{JSONObject messageObject = new JSONObject();for(ChatEndpoint chatEndpoint : webSocketCopyOnWriteArraySet){if(chatEndpoint.loginId.equals(receiverId)){chatEndpoint.session.getBasicRemote().sendText(JSONObject.toJSONString(messageObject));}}}catch(Exception e){e.printStackTrace();}}/*** 连接关闭时被调用*/@OnClosepublic void onClose(){webSocketCopyOnWriteArraySet.remove(this);}}

ServerEndpoint注解

@ServerEndpoint是一个类层次的注解,它的功能是标识当前类是一个WebSocket服务端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端。

生命周期回调方法

一个用 @ServerEndpoint 注解标记的类,可以定义以下几种方法来处理不同的 WebSocket 事件:

  • @OnOpen:在客户端打开连接时调用。
  • @OnMessage:在接收到客户端消息时调用。
  • @OnClose:在连接关闭时调用。
  • @OnError:在通信过程中发生错误时调用。

session 对象

session 对象是 Java WebSocket API 中 javax.websocket.Session 类的一个实例。它表示服务器和特定客户端之间的 WebSocket 连接。通过 session 对象,可以执行各种操作,比如发送消息(getBasicRemote().sendText())给客户端、关闭连接以及访问 WebSocket 连接的属性。

CopyOnWriteArraySet & ConcurrentHashMap

CopyOnWriteArraySet 是 Java 中一个线程安全的 Set 实现,它基于 CopyOnWriteArrayList,实现了 Set 接口。

线程安全的原因

  1. 写时复制机制:

    当执行修改操作(如添加或删除元素)时,CopyOnWriteArraySet 会复制底层的数组,创建一个新的数组并在其上进行修改操作。修改完成后,将新的数组引用替换旧的数组引用。这种机制确保了在修改操作进行期间,原有的数组不会被修改,所有正在读取操作的线程仍然可以安全地访问旧的数组。
  2. 读写分离:

    读操作直接读取底层数组,不需要加锁,因此读操作非常高效。写操作因为会复制数组,代价较高,但因为写操作相对读操作较少,这种设计在大多数应用场景中是可以接受的。

使用场景

CopyOnWriteArraySet 适用于以下场景:

  • 多线程环境中读操作远多于写操作。
  • 不需要强一致性的快速读取操作。
  • 需要线程安全但不希望在读操作上有任何性能损耗。

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

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

相关文章

基于STC8H系列单片机的定时器系统

基于STC8H系列单片机的定时器系统 STC8H4K64TL单片机介绍STC8H4K64TL单片机管脚图&#xff08;48个引脚&#xff09;STC8H4K64TL单片机串口仿真与串口通信STC8H4K64TL单片机管脚图&#xff08;32个引脚&#xff09;STC8H4K64TL单片机管脚图&#xff08;20个引脚&#xff09;STC…

Apollo使用(3):分布式docker部署

Apollo 1.7.0版本开始会默认上传Docker镜像到Docker Hub&#xff0c;可以按照如下步骤获取 一、获取镜像 1、Apollo Config Service 获取镜像 docker pull apolloconfig/apollo-configservice:${version} 我事先下载过该镜像&#xff0c;所以跳过该步骤。 2、Apollo Admin S…

计算机网络-配置路由器ACL(访问控制列表)

配置访问控制列表ACL 拓扑结构 拓扑结构如下&#xff1a; 要配置一个ACL&#xff0c;禁止PC0访问PC3&#xff0c;禁止PC4访问PC0&#xff0c;其它正常。 配置Router0 配置接口IP地址&#xff1a; interface fastethernet 0/0 ip address 192.168.1.1 255.255.255.0 no shu…

VUE 基础(一)

(直接在vscode上运行就可以&#xff0c;建一个html文件) 1 el的使用 Vue会管理el选项命中的元素及其内部的后代元素 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content…

第三部分 图论 - 第2章 最小生成树

定义 首先我们先了解下生成树的定义&#xff1a; 无向图中&#xff0c;一个连通图的最小连通子图称作该图的生成树&#xff08;不能带环&#xff0c;保持连通&#xff0c;但边要尽可能的少&#xff09;。 有n个顶点的连通图的生成树有n个顶点和n-1条边。 那么最小生成树和生成…

自动驾驶 机器人 slam 定位

自动驾驶机器人中的SLAM&#xff08;Simultaneous Localization and Mapping&#xff0c;同时定位与地图构建&#xff09;技术是一种关键技术&#xff0c;旨在使机器人在未知环境中自主导航。SLAM技术涉及机器人在移动过程中通过传感器&#xff08;如激光雷达、摄像头、IMU等&a…

决策树 和 集成学习、随机森林

决策树是非参数学习算法&#xff0c;可以解决分类问题&#xff0c;天然可以解决多分类问题&#xff08;不同于逻辑回归或者SVM&#xff0c;需要通过OVR&#xff0c;OVO的方法&#xff09;&#xff0c;也可以解决回归问题&#xff0c;甚至是多输出任务&#xff0c;并且决策树有非…

国内NAT服务器docker方式搭建rustdesk服务

前言 如果遇到10054,就不要设置id服务器!!! 由于遇到大带宽,但是又贵,所以就NAT的啦,但是只有ipv4共享和一个ipv6,带宽50MB(活动免费会升130MB~) https://bigchick.xyz/aff.php?aff322 月付-5 循环 &#xff1a;CM-CQ-Monthly-5 年付-60循环&#xff1a;CM-CQ-Annually-60官方…

数据结构第三讲:单链表的实现

数据结构第三讲&#xff1a;单链表的实现 1.什么是单链表2. 节点3.单链表的实现3.1节点的结构3.2打印单链表3.3申请一个新节点3.4单链表尾部插入3.5单链表头部插入3.6单链表的尾部删除3.7单链表头部删除3.8查找3.9在指定位置之前插入数据3.10在指定位置之后插入数据3.11删除pos…

爬虫基本库的使用之使用urllib

在Python的爬虫开发领域&#xff0c;urllib是一个非常重要的基础库。它提供了丰富的接口来发送HTTP请求并处理响应&#xff0c;非常适合初学者以及需要快速实现HTTP请求的开发者。本文将详细介绍如何使用urllib库进行基本的网络爬虫开发。 1、urllib库简介 urllib是Python标准…

安装依赖 npm install idealTree:lib: sill idealTree buildDeps 卡着不动

我一直怀疑是网络问题&#xff0c;因为等了很久也能安装成功&#xff0c;就是时间比较长&#xff0c;直到现在完全受不了了&#xff0c;决定好好整治下这个问题&#xff01; 1、执行命令 npm config get userconfig 查看配置文件所在位置&#xff0c;将其删除。 2、执行 n…

VS+opencv+环境配置

下载opencv库。 版本 - OpenCV 下载完了是一个exe文件&#xff0c;&#xff08;可以更换目录&#xff09;直接双击&#xff0c;也就是压缩。 vs配置&#xff1a; 调试-调试属性 点编辑&#xff0c;加入这两个&#xff0c;路径根据自己的opencv库 3、链接器 测试&#xff1a;…

在Postman中引用JS库

前言 在做接口测试时&#xff0c;出于安全因素&#xff0c;请求参数需要做加密或者加上签名才能正常请求&#xff0c;例如&#xff1a;根据填写的请求参数进行hash计算进行签名。postman作为主流的接口调试工具也是支持请求预处理的&#xff0c;即在请求前使用JavaScript脚本对…

windows SSH免密连接ubuntu

前提windows 和linux系统都安装了openssh服务 Linux&#xff08;安装OPENSSH服务&#xff09;&#xff1a;sudo apt-get install openssh-server Windows&#xff1a;自己百度吧 1.生成Windows公钥 Windows的CMD中执行&#xff1a;ssh-keygen -t rsa&#xff0c;执行过程中直接…

Linux编程:使用 strip 命令优化 ELF 文件大小

0. 概要 在软件开发过程中&#xff0c;经常需要处理各种各样的可执行文件和共享库。 为了提高系统的性能和减少磁盘占用空间&#xff0c;我们可能会对这些文件进行优化。其中之一就是使用 strip 命令来移除 ELF (Executable and Linkable Format) 文件中的非必要数据。 本文将…

Conda与Docker:打造无缝开发环境

Conda与Docker&#xff1a;打造无缝开发环境 在现代软件开发中&#xff0c;容器化技术已经成为一种趋势&#xff0c;它能够为应用提供一个一致的运行环境&#xff0c;无论在何处部署。Docker是实现容器化的首选工具之一。而Conda&#xff0c;作为Python和其他科学计算软件的包…

Redis:AOF持久化

1. 简介 以日志的形式来记录每个写操作&#xff0c;将redis执行的每个写操作记录下来&#xff08;读操作不记录&#xff09;&#xff0c;只需追加文件但不可以改写文件&#xff0c;redis启动之初会重新构建数据&#xff0c;即redis重启后会将日志中的所有写指令重新执行一遍以达…

扰动观测器DOB设计及其MATLAB/Simulink实现

扰动观测器(Disturbance Observer, DOB)是一种在控制系统中用于估计和补偿未知扰动的重要工具,以增强系统的鲁棒性和稳定性。其设计过程涉及系统建模、观测器结构设计以及控制律的调整。 扰动观测器设计原理 系统建模: 首先,需要建立被控对象的数学模型,明确系统的状态变…

APP重启 - C#小函数类推荐

此文记录的是一个应用程序重启的函数。 /***应用程序重启动Austin Liu 刘恒辉Project Manager and Software DesignerE-Mail: lzhdim163.comBlog: http://lzhdim.cnblogs.comDate: 2024-01-15 15:18:00使用方法&#xff1a;AppUtil.RestartApplication(Application.Executa…

2024第八届全国职工职业技能大赛“网络与信息安全管理员”赛项技术文件及任务书

2024第八届全国职工职业技能大赛“网络与信息安全管理员”赛项技术文件及任务书 一、赛项概述&#xff1a;二、竞赛形式&#xff1a;三、竞赛规则四、竞赛样题4.1、第一场4.1.2、实操闯关赛4.2、第二场4.3、第三场 需要培训可以私信博主 欢迎交流学习&#xff01; [X] &#x1…