對WebSocket做一點簡單的理解

1.概念

WebSocket 是基于 TCP 的一種新的網絡協議。它實現了瀏覽器與服務器全雙工通信——瀏覽器和服務器只需要完成一次握手,兩者之間就可以創建持久性的連接, 并進行雙向數據傳輸。

HTTP協議和WebSocket協議對比:

  • HTTP是短連接

  • WebSocket是長連接

  • HTTP通信是單向的,基于請求響應模式

  • WebSocket支持雙向通信

  • HTTP和WebSocket底層都是TCP連接

  • WebSocket缺點:

    服務器長期維護長連接需要一定的成本 各個瀏覽器支持程度不一 WebSocket 是長連接,受網絡限制比較大,需要處理好重連

    結論:WebSocket并不能完全取代HTTP,它只適合在特定的場景下使用

    WebSocket的使用場景:視頻彈幕,網頁聊天,股票基金報價實時更新, 體育實況更新

2.示例

2.1 基礎配置

導入Maven坐標

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
/*** WebSocket配置類,用于注冊WebSocket的Bean* @Author GuihaoLv*/
@Configuration
public class WebSocketConfig {
/*** 創建并返回一個 ServerEndpointExporter 實例。** ServerEndpointExporter 是 Spring 提供的一個工具類,用于自動注冊使用了 @ServerEndpoint 注解的 WebSocket 端點。* 這樣可以避免手動注冊每個 WebSocket 端點。** @return ServerEndpointExporter 實例*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
WebSocket服務器端組件:
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;/**
* WebSocket服務器端組件
* @Author GuihaoLv
*/
@Component
//標記這個類是一個WebSocket端點,可以接收來自客戶端的WebSocket連接請求。/ws/{sid}表示WebSocket的URL路徑模式,其中{sid}是路徑參數,代表會話ID(Session ID)。
@ServerEndpoint("/ws/{userId}")
public class WebSocketServer {//這里聲明了一個靜態的Map,用來存儲每個連接的Session對象,鍵是sid,值是對應的Session。通過這種方式,服務器可以追蹤每一個連接的客戶端。private static Map<String, Session> sessionMap = new ConcurrentHashMap();//一個線程安全的集合,用來存儲所有活動的WebSocketServer實例。public static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();//@OnOpen:當一個新的WebSocket連接成功建立時,此方法會被調用。@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {System.out.println("客戶端:" + userId + "建立連接");webSockets.add(this);sessionMap.put(userId, session);//把新的Session對象存入sessionMap中。}//每當從客戶端接收到消息時,該方法就會觸發。這里只是簡單地打印出收到的消息。@OnMessagepublic void onMessage(String message, @PathParam("userId") String userId) {System.out.println("收到來自客戶端:" + userId + "的信息:" + message);}//當一個WebSocket連接關閉時,此方法會被調用。它會從sessionMap中移除相應的Session對象,并輸出一條日志信息。@OnClosepublic void onClose(@PathParam("userId") String userId) {System.out.println("連接斷開:" + userId);webSockets.remove(this);sessionMap.remove(userId);}//遍歷所有現存的Session對象,并嘗試向每個客戶端發送文本消息。如果發送過程中遇到任何異常,就捕獲異常并打印堆棧跟蹤信息。public void sendToAllClient(String message) {Collection<Session> sessions = sessionMap.values();for (Session session : sessions) {try {session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}/*** 發生錯誤時調用*/@OnErrorpublic void onError(Session session, Throwable error) {System.err.println("WebSocket 發生錯誤: " + error.getMessage());error.printStackTrace();}
}

前端工具類:

const url = "ws://127.0.0.1:8080/ws/{userId}"; // 注意:這里需要替換 {userId} 為實際的用戶ID
//定義了 WebSocket 工具類的結構和方法簽名。
interface Socket {websocket: WebSocket | null;  // WebSocket 連接實例init: (userId: string) => void; // 需要傳入 userId 初始化連接send: (data: object) => void;   // 發送數據的方法onMessage: (callback: (msg) => void) => void; // 消息監聽回調onClose: (callback: () => void) => void; // 關閉回調onError: (callback: (error: Event) => void) => void; // 錯誤回調onMessageCallback: ((msg) => void) | null; // 存儲消息回調onCloseCallback: (() => void) | null; // 存儲關閉回調onErrorCallback: ((error: Event) => void) | null; // 存儲錯誤回調
}const socket: Socket = {websocket: null,//用來存儲事件回調函數。onMessageCallback: null,onCloseCallback: null,onErrorCallback: null,init: (userId: string) => {const fullUrl = url.replace("{userId}", userId); // 動態替換 userIdif (socket.websocket) return; // 避免重復連接socket.websocket = new WebSocket(fullUrl);/*** 當收到消息時調用該回調函數。*/socket.websocket.onopen = () => {console.log("WebSocket 連接成功");};/*** 當 WebSocket 連接關閉時調用該回調函數。* @param e*/socket.websocket.onclose = (e) => {console.log("WebSocket 連接關閉", e);socket.websocket = null; // 連接關閉后重置 WebSocketif (socket.onCloseCallback) {socket.onCloseCallback();}};/*** 當 WebSocket 發生錯誤時調用該回調函數。* @param e*/socket.websocket.onerror = (e) => {console.error("WebSocket 錯誤", e);if (socket.onErrorCallback) {socket.onErrorCallback(e);}};//收到 WebSocket 消息 時執行:// event.data 是接收到的字符串,先 JSON.parse() 解析成對象。// 調用 onMessageCallback 回調,如果外部監聽了消息。socket.websocket.onmessage = (event) => {try {const message = JSON.parse(event.data);console.log("📩 收到 WebSocket 消息:", message);if (socket.onMessageCallback) {socket.onMessageCallback(message);}} catch (error) {console.error("解析消息失敗:", error);}};},//消息發送 (send 方法)send: (data: object) => {if (socket.websocket && socket.websocket.readyState === WebSocket.OPEN) {socket.websocket.send(JSON.stringify(data));} else {console.log("WebSocket 未連接,嘗試重連");setTimeout(() => socket.send(data), 1000); // 嘗試重新發送消息}},//監聽消息,onMessageCallback 存儲消息回調函數,收到消息時觸發。onMessage: (callback: (msg) => void) => {socket.onMessageCallback = callback;},//監聽關閉,onCloseCallback 存儲關閉回調函數,連接關閉時觸發。onClose: (callback: () => void) => {socket.onCloseCallback = callback;},//監聽錯誤,onErrorCallback 存儲錯誤回調函數,連接錯誤時觸發。onError: (callback: (error: Event) => void) => {socket.onErrorCallback = callback;},
};export default socket;

2.2 客戶端與服務端交互示例

服務端接收消息的方法:

@OnMessage
public void onMessage(String message, @PathParam("userId") String userId) {System.out.println("收到來自客戶端:" + userId + "的信息:" + message);
}

客戶端測試頁面:

<script setup lang="ts">
import socket from '@/utils/webSocket0.ts';
import {ref} from "vue"; // 引入 WebSocket 工具類const userId = ref('');function connect() {if (!userId.value) {alert('Please enter a User ID');return;}//初始化 WebSocket 連接,傳入用戶ID作為參數。socket.init(userId.value);socket.onMessage((message) => {console.log('收到消息:', message);appendMessage(`收到消息: ${JSON.stringify(message)}`);});socket.onClose(() => {console.log('WebSocket 連接已關閉');appendMessage('WebSocket 連接已關閉');});socket.onError((error) => {console.error('WebSocket 錯誤:', error);appendMessage('WebSocket 錯誤');});
}//造一條類型為 'chat' 的消息,內容為 'Hello, Server!'。
//使用 socket.send(message); 發送消息到服務器。
//通過 appendMessage 函數在頁面上顯示已發送的消息內容。
function sendMessage() {const message = { type: 'chat', content: 'Hello, Server!' };socket.send(message);appendMessage(`發送消息: ${JSON.stringify(message)}`);
}//檢查 socket.websocket 是否存在(即 WebSocket 連接是否已經建立)。
//如果存在,則調用 close() 方法關閉連接。
function disconnect() {if (socket.websocket) {socket.websocket.close();}
}//獲取頁面上 ID 為 messages 的 div 元素。
//創建一個新的 div 元素,設置其文本內容為傳入的消息,并將其追加到 messagesDiv 中
function appendMessage(message: string) {const messagesDiv = document.getElementById('messages');if (messagesDiv) {const messageElement = document.createElement('div');messageElement.textContent = message;messagesDiv.appendChild(messageElement);}
}
</script><template><div><h1>WebSocket Client Simulation</h1><input v-model="userId" type="text" placeholder="Enter User ID"/><button @click="connect">Connect</button><button @click="sendMessage">Send Message</button><button @click="disconnect">Disconnect</button><div id="messages"></div></div>
</template><style scoped>
/* 樣式可以根據需要進行調整 */
input {margin-right: 10px;
}button {margin-right: 10px;
}#messages {margin-top: 20px;border: 1px solid #ccc;padding: 10px;width: 300px;height: 200px;overflow-y: scroll;
}
</style>

頁面原型:

連接客戶端:

客戶端向服務端發送消息:

斷開連接:

?2.3 客戶端與客戶端的交互監聽

服務端接收消息處理:

@OnMessage
public void onMessage(String message, @PathParam("userId") String userId) {System.out.println("收到來自客戶端:" + userId + "的信息:" + message);try {// 使用 FastJSON 解析 JSON 字符串JSONObject jsonMessage = JSON.parseObject(message);String toUserId = jsonMessage.getString("toUserId");// 獲取目標用戶的會話Session targetSession = sessionMap.get(toUserId);if (targetSession != null && targetSession.isOpen()) {System.out.println("正在向用戶:" + toUserId + "發送消息");// 將消息轉發給目標用戶targetSession.getBasicRemote().sendText(jsonMessage.toJSONString());} else {System.out.println("無法找到目標用戶或連接已關閉:" + toUserId);}} catch (Exception e) {e.printStackTrace();}
}

用戶A:

<template><div><h1>用戶A</h1><input v-model="message" placeholder="輸入消息" /><button @click="sendMessage">發送消息</button><div id="messages"><div v-for="(msg, index) in messages" :key="index">{{ msg }}</div></div></div>
</template><script setup lang="ts">
import { ref } from 'vue';
import socket from '@/utils/webSocket0.ts'; // 引入 WebSocket 工具類const userId = 'userA'; // 用戶A的ID
const message = ref('');
const messages = ref<string[]>([]);socket.init(userId);socket.onMessage((msg) => {console.log('收到消息:', msg);appendMessage(`收到消息: ${JSON.stringify(msg)}`);
});socket.onError((error) => {console.error('WebSocket 錯誤:', error);appendMessage('WebSocket 錯誤');
});function sendMessage() {const msgContent = { type: 'chat', content: message.value, toUserId: 'userB' };socket.send(msgContent);appendMessage(`發送消息: ${JSON.stringify(msgContent)}`);message.value = ''; // 清空輸入框
}function appendMessage(messageText: string) {messages.value.push(messageText);
}
</script><style scoped>
/* 樣式可以根據需要進行調整 */
input {margin-right: 10px;
}button {margin-right: 10px;
}#messages {margin-top: 20px;border: 1px solid #ccc;padding: 10px;width: 300px;height: 200px;overflow-y: scroll;
}
</style>

用戶B:

<template><div><h1>用戶B</h1><div id="messages"><div v-for="(msg, index) in messages" :key="index">{{ msg }}</div></div></div>
</template><script setup lang="ts">
import { ref, onMounted } from 'vue';
import socket from '@/utils/webSocket0.ts'; // 引入 WebSocket 工具類const userId = 'userB'; // 用戶B的ID
const messages = ref<string[]>([]);// 初始化 WebSocket 連接
function initSocket() {socket.init(userId);socket.onMessage((msg) => {if (msg.toUserId === userId) {console.log('收到消息:', msg);appendMessage(`收到消息: ${JSON.stringify(msg)}`);}});socket.onError((error) => {console.error('WebSocket 錯誤:', error);appendMessage('WebSocket 錯誤');});
}// 將新消息添加到消息列表中
function appendMessage(messageText: string) {messages.value.push(messageText);
}// 在組件掛載時初始化 WebSocket
onMounted(() => {initSocket();
});
</script><style scoped>
/* 樣式可以根據需要進行調整 */
#messages {margin-top: 20px;border: 1px solid #ccc;padding: 10px;width: 300px;height: 200px;overflow-y: scroll;
}
</style>

用戶A發消息給用戶B:

2.4: 一個實際的消息推送場景:

服務端消息推送處理:

//如果是回復評論
blogCommentsSaveDto.setAnswerUserId(userMapper.getUserByUsername(blogCommentsSaveDto.getAnswerUserName()));
//將回復推送給接收者
webSocketServer.sendToClient(blogCommentsSaveDto.getAnswerUserId().toString(),getUser().getId().toString(),blogCommentsSaveDto.getContent());

/*** 向指定客戶端發送文本消息* @param userId 客戶端的會話ID* @param message 要發送的消息*/
public void sendToClient(String userId, String fromId,String message) {try {// 創建標準消息結構JSONObject messageJson = new JSONObject();messageJson.put("content", message); // 原始內容放在content字段messageJson.put("timestamp", System.currentTimeMillis());messageJson.put("status", "success");messageJson.put("from",fromId);Session session = sessionMap.get(userId);if (session != null && session.isOpen()) {// 發送序列化的JSON字符串session.getBasicRemote().sendText(messageJson.toJSONString());}} catch (Exception e) {e.printStackTrace();}
}

客戶端與服務端建立連接并且實時監聽消息:

// 新增導入
import { onUnmounted } from 'vue'
import socket from '@/utils/webSocket0.ts' // 假設socket工具類路徑
import { useUserStore } from '@/stores/userStore.ts'// 新增消息通知狀態
const notifications = ref<string[]>([])
const userStore = useUserStore()// 初始化WebSocket
const initWebSocket = () => {if (!userStore.userInfo?.userId) {console.error('用戶未登錄,無法建立WebSocket連接')return}// 初始化連接socket.init(userStore.userInfo.userId.toString())// 消息監聽socket.onMessage((msg) => {console.log('收到新回復通知:', msg)notifications.value.push(msg.content)// 自動刷新評論(帶1秒延遲避免請求沖突)setTimeout(loadComments, 1000)})// 錯誤處理socket.onError((err) => {console.error('WebSocket錯誤:', err)})
}// 組件掛載時
onMounted(() => {loadComments()initWebSocket()
})

<h1>評論區</h1>
<!-- 在頂部添加通知欄 -->
<div v-if="notifications.length" class="notifications"><divv-for="(msg, index) in notifications":key="index"class="notification-item">🆕 您收到新回復:{{ msg }}</div>
</div>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/71854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/71854.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/71854.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

kali虛擬機登錄頁面發癲 大寫鎖定輸入不了密碼

不知道怎么了 總是發癲 重啟切換太麻煩了 還有時候不成功 kali其實可以開啟虛擬鍵盤 如下 就解決的 發癲kali 發癲 發癲

基于Python的商品銷量的數據分析及推薦系統

一、研究背景及意義 1.1 研究背景 隨著電子商務的快速發展&#xff0c;商品銷售數據呈現爆炸式增長。這些數據中蘊含著消費者行為、市場趨勢、商品關聯等有價值的信息。然而&#xff0c;傳統的數據分析方法難以處理海量、多源的銷售數據&#xff0c;無法滿足現代電商的需求。…

內存泄漏出現的時機和原因,如何避免?

由于時間比較緊張我就不排版了&#xff0c;但是對于每一種可能的情況都會出對應的代碼示例以及解決方案代碼示例。 內存泄漏可能的原因之一在于用戶在動態分配一個內存空間之中&#xff0c;忘記將這部分內容手動釋放。例如&#xff1a;&#xff08;c之中使用new分配內存沒有使…

PDF處理控件Aspose.PDF,如何實現企業級PDF處理

PDF處理為何成為開發者的“隱形雷區”&#xff1f; “手動調整200頁PDF目錄耗時3天&#xff0c;掃描件文字識別錯誤導致數據混亂&#xff0c;跨平臺渲染格式崩壞引發客戶投訴……” 作為開發者&#xff0c;你是否也在為PDF處理的復雜細節消耗大量精力&#xff1f;Aspose.PDF憑…

工程化與框架系列(27)--前端音視頻處理

前端音視頻處理 &#x1f3a5; 引言 前端音視頻處理是現代Web應用中的重要組成部分&#xff0c;涉及音頻播放、視頻處理、流媒體傳輸等多個方面。本文將深入探討前端音視頻處理的關鍵技術和最佳實踐&#xff0c;幫助開發者構建高質量的多媒體應用。 音視頻技術概述 前端音視…

2008-2024年中國手機基站數據/中國移動通信基站數據

2008-2024年中國手機基站數據/中國移動通信基站數據 1、時間&#xff1a;2008-2024年 2、來源&#xff1a;OpenCelliD 3、指標&#xff1a;網絡類型、網絡代數、移動國家/地區、移動網絡代碼、區域代碼、小區標識、單元標識、坐標經度、坐標緯度、覆蓋范圍、測量樣本數、坐標…

阿里云 ESA 游戲行業解決方案|安全防護、加速、低延時的技術融合

如今&#xff0c;游戲行業正處于蓬勃發展與深刻變革的關鍵時期。根據中國國際數字娛樂產業大會&#xff08;CDEC&#xff09;發布的《2024年 1-6 月中國游戲產業報告》顯示 2024 年上半年國內游戲市場實際銷售收入達 1472.67 億元&#xff0c;同比增長 2.08%&#xff0c;游戲用…

C# Unity 唐老獅 No.7 模擬面試題

本文章不作任何商業用途 僅作學習與交流 安利唐老獅與其他老師合作的網站,內有大量免費資源和優質付費資源,我入門就是看唐老師的課程 打好堅實的基礎非常非常重要: 全部 - 游習堂 - 唐老獅創立的游戲開發在線學習平臺 - Powered By EduSoho 如果你發現了文章內特殊的字體格式,…

electron + vue3 + vite 主進程到渲染進程的單向通信

用示例講解下主進程到渲染進程的單向通信 初始版本項目結構可參考項目&#xff1a;https://github.com/ylpxzx/electron-forge-project/tree/init_project 主進程到渲染進程&#xff08;單向&#xff09; 以Electron官方文檔給出的”主進程主動觸發動作&#xff0c;發送內容給渲…

【雜談】-因果性:開啟機器學習新紀元?

文章目錄 因果性&#xff1a;開啟機器學習新紀元&#xff1f;一、機器學習的現狀與局限二、因果性的定義與意義&#xff08;一&#xff09;日常生活中的因果性案例&#xff08;二&#xff09;相關性與因果性的區別 三、現有機器學習模型的困境與因果性的價值&#xff08;一&…

【Python】omegaconf 用法詳解

OmegaConf&#xff1a;從基礎到進階 1. OmegaConf 簡介 OmegaConf 是 hydra 背后的核心配置庫&#xff0c;提供比 argparse 和 json.load 更靈活的配置管理能力。其主要特性包括&#xff1a; 安裝 OmegaConf&#xff1a; pip install omegaconf2. 基本操作 2.1 創建 OmegaC…

如何在 Windows 10 啟用卓越性能模式及不同電源計劃對比

在使用 powercfg -duplicatescheme 命令啟用 “卓越性能模式”&#xff08;即 Ultimate Performance 模式&#xff09;之前&#xff0c;有幾個前提條件需要注意&#xff1a; 前提條件&#xff1a; 系統版本要求&#xff1a;卓越性能模式 僅在 Windows 10 專業版 或更高版本&a…

請談談 HTTP 中的安全策略,如何防范常見的Web攻擊(如XSS、CSRF)?

一、Web安全核心防御機制 &#xff08;一&#xff09;XSS攻擊防御&#xff08;跨站腳本攻擊&#xff09; 1. 原理與分類 ?存儲型XSS&#xff1a;惡意腳本被持久化存儲在服務端&#xff08;如數據庫&#xff09;?反射型XSS&#xff1a;腳本通過URL參數或表單提交觸發執行?…

三、0-1搭建springboot+vue3前后端分離-idea新建springboot項目

一、ideal新建項目1 ideal新建項目2 至此父項目就創建好了&#xff0c;下面創建多模塊&#xff1a; 填好之后點擊create 不刪了&#xff0c;直接改包名&#xff0c;看自己喜歡 修改包名和啟動類名&#xff1a; 打開ServiceApplication啟動類&#xff0c;修改如下&#xff1a; …

從0到1入門RabbitMQ

一、同步調用 優勢&#xff1a;時效性強&#xff0c;等待到結果后才返回 缺點&#xff1a; 拓展性差性能下降級聯失敗問題 二、異步調用 優勢&#xff1a; 耦合度低&#xff0c;拓展性強異步調用&#xff0c;無需等待&#xff0c;性能好故障隔離&#xff0c;下游服務故障不影響…

二維碼識別OCR接口:開啟高效信息提取的新篇章

前言 在數字化時代&#xff0c;二維碼作為一種高效的信息傳遞工具&#xff0c;已經廣泛應用于各個領域。而二維碼識別OCR接口的出現&#xff0c;更是為企業和開發者提供了一種快速、準確地提取信息的解決方案。 技術原理&#xff1a;圖像識別與數據解析的完美結合 二維碼識別…

ThinkPHP框架

在電腦C磁盤中安裝composer 命令 在電腦的D盤中創建cd文件夾 切換磁盤 創建tp框架 創建一個aa的網站&#xff0c;更換路徑到上一步下載的tp框架路徑 在管理中修改路徑 下載壓縮包public和view 將前面代碼中的public和view文件替換 在PHPStom 中打開文件 運行指定路徑 修改demo…

Matlab:矩陣運算篇——矩陣數學運算

目錄 1.矩陣的加法運算 實例——驗證加法法則 實例——矩陣求和 實例——矩陣求差 2.矩陣的乘法運算 1.數乘運算 2.乘運算 3.點乘運算 實例——矩陣乘法運算 3.矩陣的除法運算 1.左除運算 實例——驗證矩陣的除法 2.右除運算 實例——矩陣的除法 ヾ(&#xffe3;…

快速從C過度C++(一):namespace,C++的輸入和輸出,缺省參數,函數重載

&#x1f4dd;前言&#xff1a; 本文章適合有一定C語言編程基礎的讀者瀏覽&#xff0c;主要介紹從C語言到C過度&#xff0c;我們首先要掌握的一些基礎知識&#xff0c;以便于我們快速進入C的學習&#xff0c;為后面的學習打下基礎。 這篇文章的主要內容有&#xff1a; 1&#x…

C語言 進階指針學習筆記

文章目錄 字符指針指針數組數組指針數組名數組傳參 函數指針函數指針數組指向函數指針數組的指針 回調函數Qsort 的使用通過冒泡排序模擬實現 qsort 大部分的內容都寫在代碼注釋中 指針有類型&#xff0c;指針的類型決定了指針的整數的步長&#xff0c;指針解引用操作的時候的權…