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.pswp.cn/bicheng/75379.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/75379.shtml
英文地址,請注明出處:http://en.pswp.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->正極…