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.rhkb.cn/news/473425.html

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

相关文章

现代密码学|公钥密码体制 | RSA加密算法及其数学基础

文章目录 公钥密码RSA数学基础欧拉函数欧拉定理模指数运算 RSA加密算法对rsa的攻击 公钥密码 现代密码学&#xff5c;公钥密码体制概述 加密 A用B的公钥加密 B用B的私钥解密 认证 A使用A的私钥加密 B使用A的公钥解密 加密认证 A用A的私钥加密&#xff0c;再用B的公钥加密 B用…

VuePress v2 快速搭建属于自己的个人博客网站

目录 为什么用VuePress&#xff1f; 一、前期准备 Node.js 使用主题快速开发 二、VuePress安装 三、个性化定制 修改配置信息 删除不需要的信息 博客上传 四、部署 使用github快速部署 初始化仓库 本地配置 配置github的ssh密钥 部署 为什么用VuePress&#xff…

【阅读记录-章节1】Build a Large Language Model (From Scratch)

目录 1. Understanding large language models1.1 What is an LLM?补充介绍人工智能、机器学习和深度学习的关系机器学习 vs 深度学习传统机器学习 vs 深度学习&#xff08;以垃圾邮件分类为例&#xff09; 1.2 Applications of LLMs1.3 Stages of building and using LLMs1.4…

平台整合是网络安全成功的关键

如今&#xff0c;组织面临着日益复杂、动态的网络威胁环境&#xff0c;随着恶意行为者采用越来越阴险的技术来破坏环境&#xff0c;攻击的数量和有效性也在不断上升。我们最近的 Cyber​​Ark 身份威胁形势报告&#xff08;2024 年 5 月&#xff09;发现&#xff0c;去年 99% 的…

PlantUML——时序图

PlantUML时序图 背景 时序图&#xff08;Sequence Diagram&#xff09;&#xff0c;又名序列图、循序图&#xff0c;是一种UML交互图&#xff0c;用于描述对象之间发送消息的时间顺序&#xff0c;显示多个对象之间的动态协作。时序图的使用场景非常广泛&#xff0c;几乎各行各…

【MYSQL】分库分表

一、什么是分库分表 分库分表就是指在一个数据库在存储数据过大&#xff0c;或者一个表存储数据过多的情况下&#xff0c;为了提高数据存储的可持续性&#xff0c;查询数据的性能而进行的将单一库或者表分成多个库&#xff0c;表使用。 二、为什么要分库分表 分库分表其实是两…

Spring纯注解开发

在我的另一篇文章中&#xff08;初识Spring-CSDN博客&#xff09;&#xff0c;讲述了Bean&#xff0c;以及通过xml方式定义Bean。接下来将讲解通过注解的方法管理Bean。 我们在创建具体的类的时候&#xff0c;可以直接在类的上面标明“注解”&#xff0c;以此来声明类。 1. 常…

git push时报错! [rejected] master -> master (fetch first)error: ...

错误描述&#xff1a;在我向远程仓库push代码时&#xff0c;即执行 git push origin master命令时发生的错误。直接上错误截图。 错误截图 错误原因&#xff1a; 在网上查了许多资料&#xff0c;是因为Git仓库中已经有一部分代码&#xff0c;它不允许你直接把你的代码覆盖上去…

java常用工具包介绍

Java 作为一种广泛使用的编程语言&#xff0c;提供了丰富的标准库和工具包来帮助开发者高效地进行开发。这些工具包涵盖了从基础的数据类型操作到高级的网络编程、数据库连接等各个方面。下面是一些 Java 中常用的工具包&#xff08;Package&#xff09;及其简要介绍&#xff1…

latex中,两个相邻的表格,怎样留一定的空白

目录 问题描述 问题解决 问题描述 在使用latex写论文时&#xff0c;经常表格需要置顶写&#xff0c;则会出现两个表格连在一起的情况。下一个表名容易与上面的横线相连&#xff0c;如何通过明令&#xff0c;留出一定的空白。 问题解决 在第二个表格的 \centering命令之后…

react中如何在一张图片上加一个灰色蒙层,并添加事件?

最终效果&#xff1a; 实现原理&#xff1a; 移动到图片上的时候&#xff0c;给img加一个伪类 &#xff01;&#xff01;此时就要地方要注意了&#xff0c;因为img标签是闭合的标签&#xff0c;无法直接添加 伪类&#xff08;::after&#xff09;&#xff0c;所以 我是在img外…

C++builder中的人工智能(27):如何将 GPT-3 API 集成到 C++ 中

人工智能软件和硬件技术正在迅速发展。我们每天都能看到新的进步。其中一个巨大的飞跃是我们拥有更多基于自然语言处理&#xff08;NLP&#xff09;和深度学习&#xff08;DL&#xff09;机制的逻辑性更强的AI聊天应用。有许多AI工具可以用来开发由C、C、Delphi、Python等编程语…

【项目开发】URL中井号(#)的技术细节

未经许可,不得转载。 文章目录 前言一、# 的基本含义二、# 不参与 HTTP 请求三、# 后的字符处理机制四、# 的变化不会触发网页重新加载五、# 的变化会记录在浏览器历史中六、通过 window.location.hash 操作七、onhashchange 事件八、Google 对 # 的处理机制前言 2023 年 9 月…

AUTOSAR_EXP_ARAComAPI的7章笔记(5)

☞返回总目录 相关总结&#xff1a;典型的 SOME/IP 多绑定用例总结 7.3.3 典型的SOME/IP多绑定用例 在前面的章节中&#xff0c;我们简要提到&#xff0c;在一个典型的SOME/IP 网络协议的部署场景中&#xff0c;AP SWC不太可能自己打开套接字连接来与远程服务通信。为什么不…

Jenkins下载安装、构建部署到linux远程启动运行

Jenkins详细教程 Winodws下载安装Jenkins一、Jenkins配置Plugins插件管理1、汉化插件2、Maven插件3、重启Jenkins&#xff1a;Restart Safely插件4、文件传输&#xff1a;Publish Over SSH5、gitee插件6、清理插件&#xff1a;workspace cleanup system系统配置1、Gitee配置2、…

Flutter:Dio下载文件到本地

import dart:io; import package:dio/dio.dart;main(){// 创建dio对象final dio Dio();// 下载地址var url https://*******.org/files/1.0.0.apk;// 手机端路径String savePath Directory.systemTemp.path/ceshi.apk;print(savePath);downLoad(dio,url,savePath); }downLo…

【C++笔记】C++三大特性之多态

【C笔记】C三大特性之多态 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C三大特性之多态前言一.多态1.1 多态的概念1.2 虚函数1.3 虚函数的重写/覆盖1.4 多态的定义及实现 二.虚函数重写的⼀些其他问题2.1 协变(…

2.STM32之通信接口《精讲》之USART通信

有关通信详解进我主页观看其他文章&#xff01;【免费】SPIIICUARTRS232/485-详细版_UART、IIC、SPI资源-CSDN文库 通过以上可以看出。根据电频标准&#xff0c;可以分为TTL电平&#xff0c;RS232电平&#xff0c;RS485电平&#xff0c;这些本质上都属于串口通信。有区别的仅是…

麒麟V10,arm64,离线安装docker和docker-compose

文章目录 一、下载1.1 docker1.2 docker-compose1.3 docker.service 二、安装三、验证安装成功3.1 docker3.2 docker-compose 需要在离线环境的系统了里面安装docker。目前国产化主推的是麒麟os和鲲鹏的cpu&#xff0c;这块的教程还比较少&#xff0c;记录一下。 # cat /etc/ky…

云原生之运维监控实践-使用Telegraf、Prometheus与Grafana实现对InfluxDB服务的监测

背景 如果你要为应用程序构建规范或用户故事&#xff0c;那么务必先把应用程序每个组件的监控指标考虑进来&#xff0c;千万不要等到项目结束或部署之前再做这件事情。——《Prometheus监控实战》 去年写了一篇在Docker环境下部署若依微服务ruoyi-cloud项目的文章&#xff0c;当…