对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.mzph.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;指针解引用操作的时候的权…