SpringBoot实现WebSocket

参考链接:https://www.kancloud.cn/king_om/mic_03/2783864

一、环境搭建

1.创建SpringBoot项目,引入相关依赖

<dependencies><!-- Spring Boot核心启动器,引入常用依赖基础 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- Spring Boot对Thymeleaf模板引擎支持,用于视图渲染 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- nekohtml库,用于HTML解析,指定版本1.9.22 --><dependency><groupId>net.sourceforge.nekohtml</groupId><artifactId>nekohtml</artifactId><version>1.9.22</version></dependency><!-- JUnit 4测试框架依赖,仅测试阶段用,版本4.13.2 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!-- JUnit Jupiter(JUnit 5部分),用于测试,仅测试环境 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency><!-- Spring Boot Web开发启动器,构建Web应用相关 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot测试相关依赖,确保应用正确性等 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test</artifactId></dependency><!-- Spring Boot对WebSocket启动器,实现双向通信功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency></dependencies>

2.在resources下边创建statictemplates文件夹
3.配置application.yml

spring:thymeleaf:cache: false # 关闭Thymeleaf页面缓存,开发时便于即时看到模板修改效果encoding: UTF-8 # 模板编码设为UTF-8,确保字符正确解析,避免乱码prefix: classpath:/templates/  # 页面映射路径:模板文件查找路径前缀,在类路径下的templates目录找suffix:.html # 视图对应的模板文件后缀名mode: HTML5 # 设置模板模式为HTML5,遵循HTML5规范解析处理mvc:pathmatch:matching-strategy: ant_path_matcher # Spring MVC路径匹配采用ant_path_matcher策略,更灵活处理URL路径static-path-pattern: /static/** # 定义静态资源访问路径模式,通过/static/开头的URL可访问static目录下静态资源

二、配置类开启WebSocket支持

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @ Description: 开启WebSocket支持* 用于在Spring框架的应用中配置和启用WebSocket功能。* 通过相关注解和方法的定义,使得应用能够正确地处理WebSocket连接和通信。*/
@Configuration
public class WebSocketConfig {@Bean //用于将方法返回的ServerEndpointExporter对象作为一个Bean注册到Spring的容器中public ServerEndpointExporter serverEndpointExporter() {//创建并返回一个ServerEndpointExporter对象。// ServerEndpointExporter主要作用是扫描带有@ServerEndpoint注解的WebSocket端点类,并将它们注册到Servlet容器中,// 从而使得应用能够正确地处理WebSocket连接请求,实现WebSocket的通信功能。return new ServerEndpointExporter();}
}

三、服务层

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;/*** @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个 websocket 服务器端。* 注解的值将被用于监听用户连接的终端访问 URL 地址,客户端可以通过这个 URL 来连接到 WebSocket 服务器端*/
@Component
@Service
/**@ServerEndpoint注解用于将当前类定义为一个WebSocket服务器端端点。注解中的值"/api/websocket/{sid}"指定了客户端连接到这个WebSocket服务器端的URL地址,
其中{sid}是一个路径参数,可以在后续的方法中获取并使用,不同的客户端可以通过带有不同sid值的这个URL来建立与服务器的WebSocket连接。
*/@ServerEndpoint("/api/websocket/{sid}")
public class WebSocketServer {private Session session;  //用户信息//存放每个客户端对应的MyWebSocket对象,保存所有已连接的客户端对应的WebSocketServer实例private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();//当前在线连接数,用于记录当前有多少个客户端与WebSocket服务器建立了连接private static int onlineCount = 0;//用于接收从客户端连接URL路径参数中获取的sid值,这个sid可以用于标识不同的客户端连接或者与客户端相关的业务逻辑处理private String sid = "";/*** 连接建立成功调用的方法* 这个方法会处理与新连接建立相关的初始化操作,* 比如记录客户端的会话信息、将当前对象添加到已连接客户端集合中、更新在线连接数等。*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {this.session = session;webSocketSet.add(this);     //加入set中,将当前新建立连接的WebSocketServer对象添加到存放所有客户端的集合中this.sid = sid;addOnlineCount();           //在线数加1,调用方法增加当前的在线连接数统计值try {sendMessage("conn_success"); // 向当前新连接的客户端发送一条消息"conn_success",告知客户端连接成功。// 在控制台打印出有新窗口开始监听的sid以及当前的在线人数信息。System.out.println("有新窗口开始监听:" + sid + ",当前在线人数为:" + getOnlineCount());} catch (IOException e) {// 如果发送消息过程中出现IO异常,在控制台打印出相应提示信息System.out.println("websocket IO Exception");}}/*** 连接关闭调用的方法*/// @OnClose注解标记的方法会在WebSocket连接关闭时被自动调用。这个方法主要处理与连接关闭相关的清理操作,// 比如从已连接客户端集合中删除对应的对象、更新在线连接数等。@OnClosepublic void onClose() {webSocketSet.remove(this);  //从set中删除,将当前关闭连接的WebSocketServer对象从存放所有客户端的集合中移除subOnlineCount();           //在线数减1,调用方法减少当前的在线连接数统计值// 在控制台打印出当前关闭连接所对应的sid值System.out.println("释放的sid为:"+sid);// 在控制台打印出有一连接关闭的提示信息以及更新后的当前在线人数System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());}/*** 接受到用户信息后调用的方法* @param message* @param session*/// @OnMessage注解标记的方法会在WebSocket服务器接收到客户端发送的消息时被自动调用。这个方法主要负责处理接收到的消息,// 比如在控制台打印出消息来源及内容,并且可以根据业务需求对消息进行进一步的处理,这里是将接收到的消息群发出去。@OnMessagepublic void onMessage(String message,Session session){// 在控制台打印出收到消息的来源窗口(通过sid标识)以及消息的具体内容System.out.println("收到来自窗口" + sid + "的信息:" + message);//群发消息for (WebSocketServer item : webSocketSet) {try {//遍历所有已连接的客户端对应的WebSocketServer对象,调用每个对象的sendMessage方法将接收到的消息发送给每个客户端,实现群发功能item.sendMessage(message);} catch (IOException e) {e.printStackTrace();}}}/*** @ Param session* @ Param error*/// @OnError注解标记的方法会在WebSocket连接过程中出现错误时被自动调用。这个方法主要负责处理错误情况,// 比如在控制台打印出错误提示信息以及打印出详细的错误堆栈信息,以便于排查问题@OnErrorpublic void onError(Session session, Throwable error) {System.out.println("发生错误");error.printStackTrace();}/*** 实现服务器主动推送*/public void sendMessage(String message) throws IOException {// 通过当前客户端的会话对象(this.session)获取基本的远程通信端点(getBasicRemote),然后使用sendText方法将指定的消息发送给客户端this.session.getBasicRemote().sendText(message);}/*** 群发自定义消息*/public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {// 在控制台打印出要推送消息的目标窗口(通过sid标识)以及推送的具体内容System.out.println("推送消息到窗口" + sid + ",推送内容:" + message);for (WebSocketServer item : webSocketSet) {try {//为null则全部推送if (sid == null) {item.sendMessage(message);} else if (item.sid.equals(sid)) {// 遍历所有已连接的客户端对应的WebSocketServer对象,如果sid为null则表示要向所有客户端推送消息,// 如果当前对象的sid与要推送的目标sid相等,则调用该对象的sendMessage方法将消息发送给对应的客户端。item.sendMessage(message);}} catch (IOException e) {// 如果在发送消息给某个客户端过程中出现IO异常,跳过当前循环,继续尝试给下一个客户端发送消息。continue;}}}// 以下这几个方法用于对在线连接数(onlineCount)以及存放客户端的集合(webSocketSet)进行操作,// 并且都使用了synchronized关键字来保证在多线程环境下对这些共享资源的操作是线程安全的。/*该方法用于获取当前的在线连接数。* 由于在线连接数(onlineCount)是一个被多个方法可能同时访问和修改的共享变量,* 为了确保在多线程环境下获取到的在线连接数是准确的,使用了synchronized关键字进行同步。* 这样在某个线程调用此方法获取在线连接数时,其他线程不能同时对onlineCount进行修改操作,保证了数据的一致性。*/public static synchronized int getOnlineCount() {return onlineCount;}/*** 该方法用于增加在线连接数。* 当有新的客户端与WebSocket服务器成功建立连接时(如在onOpen方法中)会调用此方法。* 同样因为onlineCount是共享变量,多个线程可能同时尝试增加它的值(比如多个客户端同时连接),* 使用synchronized关键字确保在同一时刻只有一个线程能够执行此方法对onlineCount进行自增操作,* 避免了数据不一致的情况,比如多个线程同时增加导致计数错误的问题。*/public static synchronized void addOnlineCount() {WebSocketServer.onlineCount++;}/*** 该方法用于减少在线连接数。* 当有客户端与WebSocket服务器的连接关闭时(如在onClose方法中)会调用此方法。* 与增加在线连接数的方法类似,为了保证在多线程环境下对onlineCount进行准确的自减操作,* 使用synchronized关键字进行同步,防止多个线程同时对其进行操作而导致数据错误。*/public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}/*** 该方法用于获取存放所有已连接客户端对应的WebSocketServer对象的集合(webSocketSet)。* 虽然这里获取集合的操作相对简单,但由于webSocketSet也是一个可能被多个线程访问的共享资源,* 使用synchronized关键字进行同步,确保在获取集合时,其他线程不会对其进行修改等操作,* 从而保证获取到的集合状态是准确的,可以安全地在获取到集合后进行后续的遍历等操作(如在sendInfo方法中遍历集合发送消息)* @return*/public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {return webSocketSet;}
}

四、前端页面

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Java 后端 WebSocket 的 Tomcat 实现</title><script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head><body>
Welcome<br/><input id="text" type="text" />
<button onclick="send()">发送消息</button>
<hr/>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
<div id="message"></div>
</body>
<script type="text/javascript">// 定义一个全局变量websocket,用于存储创建的WebSocket对象,初始值为nullvar websocket = null;//判断当前浏览器是否支持WebSocketif('WebSocket' in window) {// 如果浏览器支持WebSocket,创建一个WebSocket连接对象,连接到指定的服务器地址,// 这里连接到本地的8080端口,路径为/api/websocket/100,其中100可能是一个示例的连接标识或参数websocket = new WebSocket("ws://localhost:8080/api/websocket/100");} else {// 如果浏览器不支持WebSocket,弹出一个警告框,提示用户当前浏览器不支持WebSocketalert('当前浏览器 Not support websocket')}// 连接发生错误回调方法// 当WebSocket连接过程中出现错误时,会自动调用此函数websocket.onerror = function() {// 在网页上显示"WebSocket连接发生错误"的提示信息setMessageInnerHTML("WebSocket连接发生错误");};//连接成功建立回调方法// 当WebSocket连接成功建立时,会自动调用此函数websocket.onopen = function() {// 在网页上显示"WebSocket连接成功"的提示信息setMessageInnerHTML("WebSocket连接成功");}var U01data, Uidata, Usdata//接收消息回调方法// 当WebSocket服务器发送消息过来时,会自动调用此函数websocket.onmessage = function(event) {//在控制台打印接收到的消息事件对象,用于调试查看消息的详细信息console.log(event);// 将接收到的消息内容显示在网页上setMessageInnerHTML(event.data);}//连接关闭回调方法// 当WebSocket连接关闭时,会自动调用此函数websocket.onclose = function() {setMessageInnerHTML("WebSocket连接关闭");}//监听窗口关闭事件// 当用户尝试关闭浏览器窗口时,会自动调用此函数window.onbeforeunload = function() {// 调用closeWebSocket函数,关闭当前的WebSocket连接closeWebSocket();}//将消息显示在网页上function setMessageInnerHTML(innerHTML) {//在控制台打印要显示在网页上的内容console.log(innerHTML)// 通过id获取网页上的div元素(id为"message"),并将传入的内容添加到该元素的innerHTML属性中document.getElementById('message').innerHTML += innerHTML + '<br/>';}//关闭WebSocket连接function closeWebSocket() {// 调用WebSocket对象的close方法,关闭当前建立的WebSocket连接websocket.close();}//发送消息function send() {// 通过id获取网页上文本输入框(id为"text")中的值,即用户输入的消息内容var message = document.getElementById('text').value;// 使用WebSocket对象的send方法,将用户输入的消息以特定格式(这里是一个包含"msg"字段的JSON字符串)发送给服务器websocket.send('{"msg":"' + message + '"}');// 调用setMessageInnerHTML函数,将用户输入的消息显示在网页上,并添加一个换行符(&#13;)setMessageInnerHTML(message + "&#13;");}
</script></html>

五、运行效果

两个浏览器模拟两个用户对话:
在这里插入图片描述
控制台
在这里插入图片描述

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

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

相关文章

uni-app快速入门(八)--常用内置组件(上)

uni-app提供了一套基础组件&#xff0c;类似HTML里的标签元素&#xff0c;不推荐在uni-app中使用使用div等HTML标签。在uni-app中&#xff0c;对应<div>的标签是view&#xff0c;对应<span>的是text&#xff0c;对应<a>的是navigator&#xff0c;常用uni-app…

早期超大规模语言模型的尝试——BLOOM模型论文解读,附使用MindSpore和MindNLP的模型和实验复现

背景 预训练语言模型已经成为了现代自然语言处理pipeline中的基石&#xff0c;因为其在少量的标注数据上产生更好的结果。随着ELMo、ULMFiT、GPT和BERT的开发&#xff0c;使用预训练模型在下游任务上微调的范式被广泛使用。随后发现预训练语言模型在没有任何额外训练的情况下任…

【团购核销】抖音生活服务商家应用快速接入②——商家授权

文章目录 一、前言二、授权流程三、授权Url3.1 Url参数表3.2 授权能力表3.3 源码示例 四、授权回调4.1 添加授权回调接口4.2 授权回调接口源码示例 五、实际操作演示六、参考 一、前言 目的&#xff1a;将抖音团购核销的功能集成到我们自己开发的App和小程序中 【团购核销】抖音…

解决 Android 单元测试 No tests found for given includes:

问题 报错&#xff1a; Execution failed for task :testDebugUnitTest. > No tests found for given includes: 解决方案 1、一开始以为是没有给测试类加public修饰 2、然后替换 Test 注解的包可以解决&#xff0c;将 org.junit.jupiter.api.Test 修改为 org.junit.Tes…

gbase8s之mysql的show命令实现

第一步&#xff1a;生成show.sh脚本 cat /home/gbase/show.sh #!/bin/bash #作者&#xff1a;乡村野中医 #创作时间2024-11-21 #脚本名称show.sh function show(){ #echo $# if [ "xdatabases" x$1 ] then echo "select name from sysdatabases;"|…

android 实现答题功能

一、效果 二、实现思路 1、界面实现 实现起来其实不难&#xff0c;首先我们可以看到&#xff0c;界面是由答题进度、题目、选项ABCD组成&#xff0c;现在就是要考虑实现方式&#xff0c;答题进度可以使用Textviewprogressbar实现&#xff0c;题目直接使用Textview&#xff0c;…

正排索引和倒排索引

一、简介 正排索引&#xff1a;一个未经处理的数据库中&#xff0c;一般是以文档ID作为索引&#xff0c;以文档内容作为记录。 倒排索引&#xff1a;Inverted index&#xff0c;指的是将单词或记录作为索引&#xff0c;将文档ID作为记录&#xff0c;这样便可以方便地通过单词或…

Django一分钟:django中收集关联对象关联数据的方法

场景&#xff1a;我有一个模型&#xff0c;被其它多个模型关联&#xff0c;我配置了CASCADE级联删除&#xff0c;我想要告知用户删除该实例之后&#xff0c;哪些关联数据将会被一同删除。 假设我们当前有这样一组模型&#xff1a; class Warehouse(models.Model):""…

卷积神经网络各层介绍

目录 1 卷积层 2 BN层 3 激活层 3.1 ReLU&#xff08;Rectified Linear Unit&#xff09; 3.2 sigmoid 3.3 tanh&#xff08;双曲正切&#xff09; 3.4 Softmax 4 池化层 5 全连接层 6 模型例子 1 卷积层 卷积是使用一个卷积核&#xff08;滤波器&#xff09;对矩阵进…

【工控】线扫相机小结 第四篇

背景 这一片主要是对第三篇继续补充。话说上一篇讲到了两种模式的切换&#xff0c;上一篇还遗留了一个Bug&#xff0c;在这一篇里进行订正&#xff01; 代码回顾 /// <summary>/// 其实就是打开触发/// </summary>void SetLineSacanWorkMode(){-----首先设置为帧…

AI 大模型重塑软件开发的未来

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

【软考】系统架构设计师-计算机系统基础(4):计算机网络

计算机网络功能&#xff1a;数据通信、资源共享、管理集中化、分布式处理、负载均衡 5G高峰速率&#xff1a;10Gbit/s 广域网&#xff08;因特网&#xff09;/城域网/局域网&#xff08;以太网&#xff09; 总线型&#xff1a;利用率低&#xff0c;易冲突&#xff0c;干扰大…

Ultiverse 和web3新玩法?AI和GameFi的结合是怎样

Gamef 和 AI 是我们这个周期十分看好两大赛道之一&#xff0c;(Gamef 拥有极强的破圈效应&#xff0c;引领 Web2 用户进军 Web3 最佳利器。AI是这个周期最热门赛道&#xff0c;无论 Web2的 OpenAl&#xff0c;还是 Web3&#xff0c;都成为话题热议焦点。那么结合 GamefiA1双叙事…

Matlab多输入单输出之倾斜手写数字识别

本文主要介绍使用matlab构建多输入单输出的网络架构&#xff0c;来实现倾斜的手写数字识别&#xff0c;使用concatenationLayer来拼接特征&#xff0c;实现网络输入多个特征。 1.加载训练数据 加载数据&#xff1a;手写数字的图像、真实数字标签和数字顺时针旋转的角度。 lo…

R | 统一栅格数据的坐标系、分辨率和行列号

各位同学&#xff0c;在做相关性等分析时&#xff0c;经常会遇到各栅格数据间的行列号不统一等问题&#xff0c;下面的代码能直接解决这类麻烦。以某个栅格数据的坐标系、分辨率和行列号为准&#xff0c;统一文件夹内所有栅格并输出到新的文件夹。 代码只需要更改输入输出和ti…

UE5 第一人称射击项目学习(完结)

这个项目几乎完结了。 也算我上手的第一个纯蓝图小项目。 现在只剩下缝缝补补了。 之前把子弹设计为蓝图&#xff0c;这里要引入C的面向对象思想&#xff0c;建立成员函数。 首先双击打开子弹的蓝图 这边就可以构造成员函数 写一个print your name 在这里生成成员函数后&am…

三相正弦交流电的相序:揭秘正相序与反相序的奥秘

在电力系统中&#xff0c;三相正弦交流电的应用无处不在&#xff0c;从家庭用电到大型工业设备&#xff0c;都离不开它的稳定供电。然而&#xff0c;在三相交流电中&#xff0c;有一个概念常常让初学者感到困惑&#xff0c;那就是“相序”。今天&#xff0c;我们就来深入探讨一…

力扣面试题 - 24 插入

题目&#xff1a; 给定两个整型数字 N 与 M&#xff0c;以及表示比特位置的 i 与 j&#xff08;i < j&#xff0c;且从 0 位开始计算&#xff09;。 编写一种方法&#xff0c;使 M 对应的二进制数字插入 N 对应的二进制数字的第 i ~ j 位区域&#xff0c;不足之处用 0 补齐…

华为云容器监控平台

首先搜索CCE,点击云容器引擎CCE 有不同的测试&#xff0c;生产&#xff0c;正式环境 工作负载--直接查询服务名看监控 数据库都是走的一个 Redis的查看

Spring Cloud Stream实现数据流处理

1.什么是Spring Cloud Stream&#xff1f; 我看很多回答都是“为了屏蔽消息队列的差异&#xff0c;使我们在使用消息队列的时候能够用统一的一套API&#xff0c;无需关心具体的消息队列实现”。 这样理解是有些不全面的&#xff0c;Spring Cloud Stream的核心是Stream&#xf…