SpringBoot+Vue 中 WebSocket 的使用

        WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它使得客户端和服务器之间可以进行实时数据传输,打破了传统 HTTP 协议请求 - 响应模式的限制。

        下面我会展示在 SpringBoot + Vue 中,使用WebSocket进行前后端通信。

后端

1、引入 jar 包

<dependency><!-- 引入 websocket 库,该库提供了对 WebSocket 协议的支持--><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.14</version>
</dependency>
<dependency><!-- 引入 org.json 库,该库为 Java 提供了处理 JSON 数据的功能--><groupId>org.json</groupId><artifactId>json</artifactId><version>20090211</version>
</dependency>

2、WebSocket 配置类

package com.zecApi.config;import com.zecApi.config.zecInstantMessaging.ZecInstantMessagingWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;/*** WebSocketConfig 类是一个配置类,用于配置 Spring 框架的 WebSocket 功能。* 该类实现了 WebSocketConfigurer 接口,并重写了 registerWebSocketHandlers 方法,* 用于注册 WebSocket 处理器和配置相关的连接信息。*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {static {System.out.println("----------------------------------");System.out.println("------   WebSocket服务启动   -------");System.out.println("----------------------------------");}/*** 配置 WebSocket 容器的参数* @return ServletServerContainerFactoryBean 实例*/@Beanpublic ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();// 设置最大文本消息缓冲区大小为 8192 字节container.setMaxTextMessageBufferSize(8192);// 设置最大二进制消息缓冲区大小为 8192 字节container.setMaxBinaryMessageBufferSize(8192);return container;}/*** 重写 WebSocketConfigurer 接口的 registerWebSocketHandlers 方法,* 该方法用于注册 WebSocket 处理器并配置连接的相关信息。** @param registry 用于注册 WebSocket 处理器的注册表对象*/@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册WebSocket处理器,并设置允许的来源registry.addHandler(zecInstantMessagingWebSocketHandler(), "/ZecInstantMessaging/ZecInstantMessagingWebSocketHandler/{account}").setAllowedOrigins("*"); // 允许所有来源,生产环境建议指定具体的域}/*** 定义一个名为 ZecInstantMessagingWebSocketHandler 的 Bean,该 Bean 是一个自定义的 WebSocket 处理器。* Spring 会将该 Bean 注入到应用程序中,以便在 WebSocket 连接时使用。** @return 返回一个 ZecInstantMessagingWebSocketHandler 实例*/@Beanpublic ZecInstantMessagingWebSocketHandler zecInstantMessagingWebSocketHandler() {return new ZecInstantMessagingWebSocketHandler();}}

3、WebSocket 处理器类 

package com.zecApi.config.zecInstantMessaging;import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;/*** 自定义的 WebSocket 处理器类,继承自 TextWebSocketHandler,用于处理 WebSocket 连接、消息收发和连接关闭等操作。*/
//@Component
@ServerEndpoint("/ZecInstantMessaging/ZecInstantMessagingWebSocketHandler/{account}")
public class ZecInstantMessagingWebSocketHandler extends TextWebSocketHandler {/*** 用于存储账号和 WebSocketSession 映射关系的并发哈希表。* 键为账号,值为对应的 WebSocketSession 对象,方便根据账号查找对应的会话。* 采用 ConcurrentHashMap 保证在多线程环境下的线程安全。*/private static final ConcurrentHashMap<String, WebSocketSession> sessionPool = new ConcurrentHashMap<>();/*** 用于存储所有 WebSocketSession 的并发列表。* 该列表用于存储所有当前活跃的 WebSocket 会话,方便进行广播等操作。* 采用 CopyOnWriteArrayList 保证在多线程环境下的线程安全。*/private static final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();/*** 当与客户端的 WebSocket 连接建立成功后,此方法会被调用。** @param session 代表与客户端建立的 WebSocket 会话对象。*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) {try {// 从 URI 中获取 account 参数String account = extractAccountFromSession(session);// 检查是否成功获取到账号信息if (account == null) {// 若未获取到账号信息,打印错误信息并关闭连接System.out.println("客户端连接失败,未获取到账号信息");session.close();return;}// 打印客户端连接成功信息,包含账号信息System.out.println("账号 " + account + " 已上线");// 将账号和对应的 WebSocketSession 存入 sessionPool 中sessionPool.put(account, session);// 将该 WebSocketSession 存入 sessions 列表中sessions.add(session);// 遍历 sessionPool 中的所有账号,打印在线账号信息for (String key : sessionPool.keySet()) {System.out.println("在线账号: " + key);}// 打印当前在线人数System.out.println("在线人数:" + sessionPool.size());} catch (IOException e) {// 处理关闭连接时可能出现的异常System.out.println("处理连接建立时出现 I/O 异常: " + e.getMessage());}}/*** 当接收到客户端发送的文本消息时,此方法会被调用。** @param session 代表与客户端建立的 WebSocket 会话对象。* @param message 客户端发送的文本消息对象。*/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) {try {// 从接收到的 TextMessage 对象中提取消息的具体内容String payload = message.getPayload();// 打印接收到的消息内容,方便调试和监控System.out.println("收到客户端发来的消息: " + payload);// 解析 JSON 数据JSONObject jsonObject = new JSONObject(payload);// 提取消息 IDlong messageId = jsonObject.optLong("id", -1);if (messageId == -1) {System.out.println("未找到有效的消息 ID");}// 提取真正的消息内容StringBuilder content = new StringBuilder();for (int i = 0; ; i++) {String charStr = jsonObject.optString(String.valueOf(i));if (charStr.isEmpty()) {break;}content.append(charStr);}// 真正的信息String msg = content.toString();//把发来的信息按照 / 分成数组String[] splitMsg = msg.split("/");switch (splitMsg[0]){case  "chat":System.out.println("聊天来了");break;default:System.out.println("默认");}// 打印提取的消息内容System.out.println("提取的消息内容: " + msg);// 向发送消息的客户端回送一条确认消息,告知服务器已成功收到消息// 创建一个 JSONObject 对象JSONObject response = new JSONObject();// 向 JSONObject 中添加键值对,键为 "friendAccount",值为 "friendAccount"(这里的值用的是前端传回来的)response.put("friendAccount", splitMsg[2]);// 将 messageId 添加到响应中(因为前端在发送信息后会校验返回数据的id,从而结束监听计时器,如果没有 id,连接计时器不会停止,导致超时报错)response.put("id", messageId);// 将 JSONObject 转换为 JSON 格式的字符串String jsonString = response.toString();// 把信息发送给前端session.sendMessage(new TextMessage(jsonString));} catch (IOException e) {// 若在处理消息或发送确认消息过程中出现 I/O 异常,捕获该异常// 并打印错误信息,包含异常的具体描述,便于后续排查问题System.out.println("处理消息时出现异常: " + e.getMessage());} catch (JSONException e) {throw new RuntimeException(e);}}/*** 当与客户端的 WebSocket 连接关闭时,此方法会被调用。** @param session 代表与客户端建立的 WebSocket 会话对象。* @param status 表示连接关闭的状态信息。*/@Overridepublic void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) {// 从 WebSocketSession 的 URI 中提取账号信息String account = extractAccountFromSession(session);// 检查是否成功获取到账号信息if (account != null) {// 从 sessionPool 中移除该账号对应的 WebSocketSessionsessionPool.remove(account);// 从 sessions 列表中移除该 WebSocketSessionsessions.remove(session);// 打印客户端断开连接信息,包含账号信息和当前在线人数System.out.println("账号 " + account + " 已下线" );// 打印当前在线人数System.out.println("在线人数:" + sessionPool.size());}}/*** 从 WebSocketSession 的 URI 中提取账号信息。** @param session 代表与客户端建立的 WebSocket 会话对象。* @return 提取到的账号信息,如果未找到则返回 null。*/private String extractAccountFromSession(WebSocketSession session) {if (session.getUri() == null) {return null;}// 获取 WebSocketSession 的 URI 并转换为字符串String uri = session.getUri().toString();// 查找 URI 中最后一个斜杠的位置int index = uri.lastIndexOf("/");// 检查是否找到斜杠且斜杠后面还有字符if (index != -1 && index < uri.length() - 1) {// 提取斜杠后面的部分作为账号信息return uri.substring(index + 1);}// 若未找到合适的账号信息,返回 nullreturn null;}
}

前端

1、WebSocket 工具JS

        这个是用来连接 webSocket 的。

// 定义 WebSocket 实例变量,用于存储当前的 WebSocket 连接
let webSocket;
// 我这里本来是用 sessionStorage 来获取登录账号的,这边演示的话我就直接写死了。
// const account = getAccountBySessionStorage();
const account = "987654321";/*** WebSocket还有一个readyState属性,可以用来获取当前连接的状态。* readyState有四个可能的值:0(连接尚未建立)、1(连接已建立,可以通信)、2(连接正在关闭)、3(连接已关闭)*//*** 登录时连接webSocket* @param account* 这个是我这边登录后进行 websocket 连接,这里演示没用到,下面刷新也可以进行连接*/
// export function loginConnectWebSocket(account) {
//     if (account === null){
//         return;
//     }else {
//         if (!webSocket || webSocket.readyState === WebSocket.CLOSED){
//             return connectWebSocket(account);
//         }
//     }
// }/*** 监听页面刷新,重新进行 WebSocket 连接*/
window.onload = function() {console.log("刷新了");if (account === null){return;}else {if (!webSocket || webSocket.readyState === WebSocket.CLOSED){connectWebSocket(account);}}
};/*** 监听页面关闭事件,页面刷新前也会触发(页面刷新会自动断开websocket连接,这个暂时不需要)*/
// window.onbeforeunload = function (){
//     disconnectWebSocket();
// };/*** 获取当前的 WebSocket 实例* @returns {WebSocket|null} 当前的 WebSocket 实例或 null*/
export function getWebSocket() {return webSocket;
}/*** 主动断开 WebSocket 连接*/
export function disconnectWebSocket() {if (webSocket && webSocket.readyState !== WebSocket.CLOSED) {webSocket.close();webSocket = null;}
}/*** 封装一个连接 webSocket 的操作*/
export function connectWebSocket(account){return new Promise((resolve, reject) => {// 检查是否已经存在有效的 WebSocket 连接if (webSocket && webSocket.readyState === WebSocket.OPEN) {return resolve(webSocket);}// 如果存在连接但已经关闭,先断开if (webSocket && webSocket.readyState !== WebSocket.CLOSED) {// console.log("断开了");webSocket.close();}webSocket = new WebSocket(`ws://localhost:8088/ZecInstantMessaging/ZecInstantMessagingWebSocketHandler/${account}`);// 连接成功时的处理webSocket.onopen = function () {// console.log('WebSocket 连接已建立');resolve(webSocket);};// 连接错误时的处理webSocket.onerror = function (error) {// console.error('WebSocket 连接错误:', error);reject(error);};// 连接关闭时的处理webSocket.onclose = function () {// console.log('WebSocket 连接已关闭');};});
}

2、VUE 页面

<template><div style="width: 100%;height: 120px;"><div><textarea v-model="message" style="height: 50px;resize: none; width: 97%;font-family: 'Arial'; font-size: 14px;padding: 5px"></textarea></div><div><el-button style="margin-top: 5px;position: fixed; right: 12px;" @click="sendMessage">发送</el-button></div></div>
</template><script>
import {ref} from "vue";
import {sendMessageJS} from "@/module/zec-instant-messaging/api/MessagePage";export default {name: "DataScreen",setup(){let message = ref("");// 聊天页面记录的好友的账号(我这里单独写一个页面就直接写死账号了,主要用来演示 websocket,你们用的话可以替换成自己的)let friendAccount = ref("123456789");// 我的账号let myAccount = ref("987654321");// 发送消息const sendMessage = async () => {const msg = "chat" + "/" + myAccount.value + "/" + friendAccount.value + "/" + message.value + "/注释:标识、我的账号、好友的账号、信息";// 开始发送try {// 这里进行发送const response = await sendMessageJS(msg);// 如果发送成功,才会执行下面的语句console.log('信息发送成功,并接收到后端返回的信息:', response);console.log("id:"+response.id);console.log("friendAccount::"+response.friendAccount);// 执行后续代码} catch (error) {// 如果信息发送失败,就执行下面的语句console.error('信息发送失败:', error);}};return{sendMessage,message}}
}
</script><style scoped></style>

3、页面 JS

import {getWebSocket} from "@/common/webSocketUtil";// 发送信息的函数
export async function sendMessageJS(msg) {const webSocket = getWebSocket();return new Promise((resolve, reject) => {// 检查WebSocket连接状态if (webSocket.readyState !== WebSocket.OPEN) {reject(new Error('WebSocket 连接未打开!'));return;}// 生成唯一的消息IDconst messageId = Date.now();const message = { ...msg, id: messageId };// 设置超时时间const timeoutId = setTimeout(() => {reject(new Error('信息发送超时'));webSocket.removeEventListener('message', handleMessage);webSocket.removeEventListener('error', handleError);}, 5000); // 5秒超时// 定义处理消息响应的函数const handleMessage = (event) => {const response = JSON.parse(event.data);if (response.id === messageId) {clearTimeout(timeoutId);resolve(response);// 移除事件监听器webSocket.removeEventListener('message', handleMessage);webSocket.removeEventListener('error', handleError);}};// 定义处理错误的函数const handleError = (error) => {clearTimeout(timeoutId);reject(error);// 移除事件监听器webSocket.removeEventListener('message', handleMessage);webSocket.removeEventListener('error', handleError);};// 添加事件监听器webSocket.addEventListener('message', handleMessage);webSocket.addEventListener('error', handleError);// 发送消息webSocket.send(JSON.stringify(message));});
}

测试

后端打印

         这是后端接收到前端的信息。

 前端打印

        这是发送信息到后端,并接收后端返回来的数据。

代码具体功能我都写有注释,如果有问题可以联系我进行调整。

这是我单独拎出来写的,如果有问题可以联系我进行调整。

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

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

相关文章

STM32 FATFS - 在SDIO的SD卡中运行fatfs

参考文章 STM32CubeMX | SD Card FATFS - 知乎 [STM32F4]基于F407的硬件移植Free RTOSFATFS&#xff08;SDIO&#xff09;_freertosfatfs-CSDN博客 例程地址&#xff1a;STM32FatFS: 基于stm32的fatfs例程&#xff0c;配合博客文章 基于梁山派天空星开发板&#xff0c;STM3…

Java 进化之路:从 Java 8 到 Java 21 的重要新特性

Java 进化之路&#xff1a;从 Java 8 到 Java 21 的重要新特性 开篇介绍 在软件开发领域&#xff0c;Java 作为一门历史悠久且广泛应用的编程语言&#xff0c;始终保持着其核心竞争力和持续创新能力。自 Java 8 发布以来&#xff0c;Java 经历了一系列重要版本更新&#xff0…

Reactor 事件流 vs. Spring 事件 (ApplicationEvent)

Reactor 事件流 vs. Spring 事件 ApplicationEvent Reactor 事件流 vs. Spring 事件 (ApplicationEvent)1️⃣ 核心区别2️⃣ Spring 事件 (ApplicationEvent)✅ 示例&#xff1a;Spring 事件发布 & 监听1️⃣ 定义事件2️⃣ 发布事件3️⃣ 监听事件&#x1f539; 进阶&…

JVM生产环境问题定位与解决实战(六):总结篇——问题定位思路与工具选择策略

本文已收录于《JVM生产环境问题定位与解决实战》专栏&#xff0c;完整系列见文末目录 引言 在前五篇文章中&#xff0c;我们深入探讨了JVM生产环境问题定位与解决的实战技巧&#xff0c;从基础的jps、jmap、jstat、jstack、jcmd等工具&#xff0c;到JConsole、VisualVM、MAT的…

【5090d】配置运行和微调大模型所需基础环境【一】

RuntimeError: Failed to import transformers.integrations.bitsandbytes because of the following error (look up to see its traceback): No module named triton.ops 原因&#xff1a;是因为在导入 transformers.integrations.bitsandbytes 时缺少必要的依赖项 triton.op…

华为交换综合实验——VRRP、MSTP、Eth-trunk、NAT、DHCP等技术应用

一、实验拓扑 二、实验需求 1,内网Ip地址使用172.16.0.0/16分配 2,sw1和SW2之间互为备份 3, VRRP/STP/VLAN/Eth-trunk均使用 4,所有Pc均通过DHCP获取IP地址 5,ISP只能配置IP地址 6,所有电脑可以正常访问IsP路由器环回 三、需求分析 1、设备连接需求 二层交换机&#xff08;LS…

DeepSeek 开源的 3FS 如何?

DeepSeek 3FS&#xff08;Fire-Flyer File System&#xff09;是一款由深度求索&#xff08;DeepSeek&#xff09;于2025年2月28日开源的高性能并行文件系统&#xff0c;专为人工智能训练和推理任务设计。以下从多个维度详细解析其核心特性、技术架构、应用场景及行业影响&…

Qt实现HTTP GET/POST/PUT/DELETE请求

引言 在现代应用程序开发中&#xff0c;HTTP请求是与服务器交互的核心方式。Qt作为跨平台的C框架&#xff0c;提供了强大的网络模块&#xff08;QNetworkAccessManager&#xff09;&#xff0c;支持GET、POST、PUT、DELETE等HTTP方法。本文将手把手教你如何用Qt实现这些请求&a…

echarts+HTML 绘制3d地图,加载散点+散点点击事件

首先&#xff0c;确保了解如何本地引入ECharts库。 html 文件中引入本地 echarts.min.js 和 echarts-gl.min.js。 可以通过官网下载或npm安装&#xff0c;但这里直接下载JS文件更简单。需要引入 echarts.js 和 echarts-gl.js&#xff0c;因为3D地图需要GL模块。 接下来是HTM…

深度剖析 MySQL 与 Redis 缓存一致性:理论、方案与实战

在当今的互联网应用开发中&#xff0c;MySQL 作为可靠的关系型数据库&#xff0c;与 Redis 这一高性能的缓存系统常常协同工作。然而&#xff0c;如何确保它们之间的数据一致性&#xff0c;成为了开发者们面临的重要挑战。本文将深入探讨 MySQL 与 Redis 缓存一致性的相关问题&…

DAO 类的职责与设计原则

1. DAO 的核心职责 DAO&#xff08;Data Access Object&#xff0c;数据访问对象&#xff09;的主要职责是封装对数据的访问逻辑&#xff0c;但它与纯粹的数据实体类&#xff08;如 DTO、POJO&#xff09;不同&#xff0c;也与 Service 业务逻辑层不同。 DAO 应该做什么&…

【Kubernetes】如何使用 kubeadm 搭建 Kubernetes 集群?还有哪些部署工具?

使用 kubeadm 搭建 Kubernetes 集群是一个比较常见的方式。kubeadm 是 Kubernetes 提供的一个命令行工具&#xff0c;它可以简化 Kubernetes 集群的初始化和管理。下面是使用 kubeadm 搭建 Kubernetes 集群的基本步骤&#xff1a; 1. 准备工作 确保你的环境中有两台或更多的机…

Pycharm(十二)列表练习题

一、门和钥匙 小X在一片大陆上探险&#xff0c;有一天他发现了一个洞穴&#xff0c;洞穴里面有n道门&#xff0c; 打开每道门都需要对应的钥匙&#xff0c;编号为i的钥匙能用于打开第i道门&#xff0c; 而且只有在打开了第i(i>1)道门之后&#xff0c;才能打开第i1道门&#…

在未归一化的线性回归模型中,特征的尺度差异可能导致模型对特征重要性的误判

通过数学公式来更清晰地说明归一化对模型的影响&#xff0c;以及它如何改变特征的重要性评估。 1. 未归一化的情况 假设我们有一个线性回归模型&#xff1a; y β 0 β 1 x 1 β 2 x 2 ϵ y \beta_0 \beta_1 x_1 \beta_2 x_2 \epsilon yβ0​β1​x1​β2​x2​ϵ 其…

JS—页面渲染:1分钟掌握页面渲染过程

个人博客&#xff1a;haichenyi.com。感谢关注 一. 目录 一–目录二–页面渲染过程三–DOM树和渲染树 二. 页面渲染过程 浏览器的渲染过程可以分解为以下几个关键步骤 2.1 解析HTML&#xff0c;形成DOM树 浏览器从上往下解析HTML文档&#xff0c;将标签转成DOM节点&#…

niuhe插件, 在 go 中渲染网页内容

思路 niuhe 插件生成的 go 代码是基于 github.com/ma-guo/niuhe 库进行组织管理的, niuhe 库 是对 go gin 库的一个封装&#xff0c;因此要显示网页, 可通过给 gin.Engine 指定 HTMLRender 来实现。 实现 HTMLRender 我们使用 gitee.com/cnmade/pongo2gin 实现 1. main.go …

openEuler24.03 LTS下安装HBase集群

前提条件 安装好Hadoop完全分布式集群&#xff0c;可参考&#xff1a;openEuler24.03 LTS下安装Hadoop3完全分布式 安装好ZooKeeper集群&#xff0c;可参考&#xff1a;openEuler24.03 LTS下安装ZooKeeper集群 HBase集群规划 node2node3node4MasterBackup MasterRegionServ…

LVGL移植说明

https://www.cnblogs.com/FlurryHeart/p/18104596 参考&#xff0c;里面说明了裸机移植以及freeRTOS系统移植。 移植到linux https://blog.csdn.net/sunchao124/article/details/144952514

ubuntu虚拟机裁剪img文件系统

1. 定制文件系统前期准备 将rootfs.img文件准备好&#xff0c;并创建target文件夹2. 挂载文件系统 sudo mount rootfs.img target #挂载文件系统 sudo chroot target #进入chroot环境3. 内裁剪文件系统 增删裁剪文件系统 exit #退出chroot环境 sudo umount target…

esp826601s固件烧录方法(ch340+面包板)

esp826601s固件烧录方法&#xff08;ch340面包板&#xff09; 硬件 stm32f10c8t6&#xff0c;esp826601s,面包板&#xff0c;ch340(usb转ttl),st_link&#xff08;供电&#xff09; 接线 烧录时&#xff1a; stm32f10c8t6&#xff1a;gnd->负极&#xff0c; 3.3->正极…