Springboot 集成 WebSocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
1.添加依赖包
<!--webSocket-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.添加WebSocket配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
3.编写web服务端
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;@Component
@ServerEndpoint("/websocket/{clientId}")
@Slf4j
public class WebSocketServer {/*** 客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 客户端id*/private String clientId;/*** 用来存放每个客户端对应的MyWebSocket对象*/private static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();/*** 用来存在线连接用户信息*/private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<String, Session>();/*** 链接成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam(value = "clientId") String clientId) {try {this.session = session;this.clientId = clientId;webSockets.add(this);sessionPool.put(clientId, session);log.info("【websocket消息】有新的连接 clientId:{},总数为:{}", clientId, webSockets.size());} catch (Exception e) {log.info("【websocket消息】有新的连接构建失败 clientId:{},总数为:{},error:", clientId, webSockets.size(), e);}}/*** 链接关闭调用的方法*/@OnClosepublic void onClose() {try {webSockets.remove(this);sessionPool.remove(this.clientId);log.info("【websocket消息】连接断开 clientId:{},总数为:{}", this.clientId, webSockets.size());} catch (Exception e) {log.info("【websocket消息】连接断开失败 clientId:{},总数为:{},error:", clientId, webSockets.size(), e);}}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message) {log.info("【websocket消息】收到客户端消息:{}", message);}/*** 发送错误时的处理** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("【websocket消息】发生错误,原因:", error);}/*** 此为广播消息*/public void sendBroadcastMessage(String message) {log.info("【websocket消息】广播消息:{}", message);for (WebSocketServer webSocket : webSockets) {try {if (webSocket.session.isOpen()) {webSocket.session.getAsyncRemote().sendText(message);}} catch (Exception e) {log.error("【websocket消息】广播消息异常,消息:{},error:", message, e);}}}/*** 此为单点消息*/public void sendSinglePointMessage(String targetId, String message) {Session session = sessionPool.get(targetId);if (session != null && session.isOpen()) {try {session.getAsyncRemote().sendText(message);log.info("【websocket消息】单点消息,targetId:{},消息:{}", targetId, message);} catch (Exception e) {log.error("【websocket消息】单点消息异常,targetId:{},消息:{},error:", targetId, message, e);}}}}
4.html测试页面
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Websocket客户端</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>var socket;function openSocket() {const socketUrl = "ws://localhost:8081/websocket/" + $("#clientId").val();console.log(socketUrl);if (socket != null) {socket.close();socket = null;}socket = new WebSocket(socketUrl);// 开启WebSocket连接socket.onopen = function () {console.log("websocket已开启");};// 获得消息事件socket.onmessage = function (msg) {console.log(msg.data);};// 关闭事件socket.onclose = function () {console.log("websocket已关闭");};// 发生了错误事件socket.onerror = function () {console.log("websocket发生了错误");}}function sendMessage() {socket.send('{"targetId":"' + $("#targetId").val() + '","contentText":"' + $("#contentText").val() + '"}');console.log('{"targetId":"' + $("#targetId").val() + '","contentText":"' + $("#contentText").val() + '"}');}
</script>
<body><div>客户端身份标识ID<input id="clientId" type="text" value="10"><button style="color: cornflowerblue"><a onclick="openSocket()">开启WebSocket连接</a></button>
</div>
<br><br>
<div>客户端向服务器发送的内容<input id="targetId" type="text" value="20"><input id="contentText" type="text" value="hello WebSocket"><button style="color: cornflowerblue"><a onclick="sendMessage()">发送消息</a></button>
</div></body>
</html>
5.模拟服务端发送消息
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;@Component
@ServerEndpoint("/websocket/{clientId}")
@Slf4j
public class WebSocketServer {/*** 客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 客户端id*/private String clientId;/*** 用来存放每个客户端对应的MyWebSocket对象*/private static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();/*** 用来存在线连接用户信息*/private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<String, Session>();/*** 链接成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam(value = "clientId") String clientId) {try {this.session = session;this.clientId = clientId;webSockets.add(this);sessionPool.put(clientId, session);log.info("【websocket消息】有新的连接 clientId:{},总数为:{}", clientId, webSockets.size());} catch (Exception e) {log.info("【websocket消息】有新的连接构建失败 clientId:{},总数为:{},error:", clientId, webSockets.size(), e);}}/*** 链接关闭调用的方法*/@OnClosepublic void onClose() {try {webSockets.remove(this);sessionPool.remove(this.clientId);log.info("【websocket消息】连接断开 clientId:{},总数为:{}", this.clientId, webSockets.size());} catch (Exception e) {log.info("【websocket消息】连接断开失败 clientId:{},总数为:{},error:", clientId, webSockets.size(), e);}}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message) {log.info("【websocket消息】收到客户端消息:{}", message);}/*** 发送错误时的处理** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("【websocket消息】发生错误,原因:", error);}/*** 此为广播消息*/public void sendBroadcastMessage(String message) {log.info("【websocket消息】广播消息:{}", message);for (WebSocketServer webSocket : webSockets) {try {if (webSocket.session.isOpen()) {webSocket.session.getAsyncRemote().sendText(message);}} catch (Exception e) {log.error("【websocket消息】广播消息异常,消息:{},error:", message, e);}}}/*** 此为单点消息*/public void sendSinglePointMessage(String targetId, String message) {Session session = sessionPool.get(targetId);if (session != null && session.isOpen()) {try {session.getAsyncRemote().sendText(message);log.info("【websocket消息】单点消息,targetId:{},消息:{}", targetId, message);} catch (Exception e) {log.error("【websocket消息】单点消息异常,targetId:{},消息:{},error:", targetId, message, e);}}}}
发送广播消息:http://localhost:8081/api//websocket/sendBroadcastMessage?message=hello
发送点对点消息:http://localhost:8081/api//websocket/sendSinglePointMessage?message=hello&targetId=10
相关文章:
Springboot 集成 WebSocket
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接…...
谨以此篇,纪念我2023年曲折的计算机保研之路
目录 阶段一:迷茫阶段二:准备个人意愿保研材料准备套磁老师5.1日 浙大线上编程测试5.8日 浙大线上面试 —— 一面5.17日 浙大线上面试——二面5.29日 实验室面试结果5.27日 南开线上面试6.20日 华师电话面试 阶段三:旅途北航CS(6.…...
VSS、VDD、VBAT、VSSA
引言 在学习设计TM32时,发现芯片除了GPIO引脚外还会引出许多引脚,以STM32F407ZGT6为例除了GPIO引脚还会有以下引脚 如VSS、VDD、VBAT、VSSA、NRST、VREF、VDDA、VCAP_1、VCAP_2、PDR_ON这些引脚。他们有何作用,电路设计中应如何连接&#x…...
【Rust基础③】方法method、泛型与特征
文章目录 6 方法 Method6.1 定义方法self、&self 和 &mut self 6.2 自动引用和解引用6.3 关联函数 7 泛型和特征7.1 泛型 Generics7.1.1 结构体中使用泛型7.1.2 枚举中使用泛型7.1.3 方法中使用泛型为具体的泛型类型实现方法 7.1.4 const 泛型 7.2 特征 Trait7.2.1 为类…...
48.排列问题求解
思路分析:通过为每一队分配一个id,join条件要求t1.num < t2.num实现相同两队只比一次 代码实现: with t as (SELECT team_name,caseteam_nameWHEN 勇士 then 1WHEN 湖人 then 2WHEN 灰熊 then 3else 4end numFROM team )SELECT t1.team_…...
18.(开发工具篇Gitlab)Git如何回退到指定版本
首先: 使用git log命令查看提交历史,找到想要回退的版本的commit id. 使用git reset命令 第一步:git reset --hard 命令是强制回到某一个版本。执行后本地工程回退到该版本。 第二步:利用git push -f命令强制推到远程 如下所示: 优点:干净利落,回滚后完全回到最初状态…...
IDEA初始配置
1. 详细设置 安装完IDEA之后的简单配置。 1.1 如何打开详细配置界面 1、显示工具栏 2、选择详细配置菜单或按钮 1.2 系统设置 1、默认启动项目配置 启动IDEA时,默认自动打开上次开发的项目?还是自己选择? 如果去掉Reopen projects on …...
WM_COPYDATA传回返回值的一个方案
方案背景 适应场景,通过WM_COPYDATA进行进程间通信时,SendMessage不能返回自定义的数据,由此想到以下思路解决这个问题 A进程使用VirtualAlloc分配一块内存,通过某种方式将此地址以及A进程ID传给另一个进程B B进程使用OpenProce…...
【日常业务开发】接口性能优化
【日常业务开发】接口性能优化 缓存本地缓存分布式缓存 数据库分库分表SQL 优化 业务程序并行化异步化池化技术预先计算事务粒度批量读写锁的粒度尽快return上下文传递空间换时间集合空间大小 缓存 本地缓存 本地缓存,最大的优点是应用和cache同一个进程内部&…...
Android 10.0 禁止弹出系统simlock的锁卡弹窗功能实现
1.前言 在10.0的系统开发中,在一款产品中,需要实现simlock锁卡功能,在系统实现锁卡功能以后,在开机的过程中,或者是在插入sim卡 后,当系统检测到是禁用的sim卡后,就会弹出simlock锁卡弹窗,要求输入puk 解锁密码,功能需求禁用这个弹窗,所以就需要看是 哪里弹的,禁用…...
VulnHub lazysysadmin
一、信息收集 1.nmap扫描开发端口 开放了:22、80、445 访问80端口,没有发现什么有价值的信息 2.扫描共享文件 enum4linux--扫描共享文件 使用: enum4linux 192.168.103.182windows访问共享文件 \\192.168.103.182\文件夹名称信息收集&…...
ppt怎么压缩到10m以内?分享ppt缩小方法
在日常工作中,我们常常需要制作和分享PowerPoint演示文稿,然而,有时候文稿中的图片、视频等元素会导致文件过大,无法在电子邮件或其他平台上顺利传输。为了将PPT文件压缩到10M以内,我们可以使用一些专门的文件压缩工具…...
智能警用装备管理系统-科技赋能警务
警用物资装备管理系统(智装备DW-S304)是依托互云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对警用装备进行统一管理、分析的信息化、智能化、规范化的系统。 (1)感知智能化 装备感知是整个方案的基础,本方…...
攻防千层饼
近年来,网络安全领域正在经历一场不断升级的攻防对抗,这场攻防已经不再局限于传统的攻击与防御模式,攻击者和防守者都已经越发熟练,对于传统攻防手法了如指掌。 在这个背景下,攻击者必须不断寻求创新的途径࿰…...
组件封装使用?
组件封装是指在软件开发中,将功能代码或数据封装成一个独立的、可重用的模块或组件。这种封装可以使得代码更加模块化、可维护性和可重用性。在许多编程语言和开发框架中,都有不同的方式来实现组件封装。 以下是一些常见的组件封装方法和技巧࿱…...
2.3 初探Hadoop世界
文章目录 零、学习目标一、导入新课二、新课讲解(一)Hadoop的前世今生1、Google处理大数据三大技术2、Hadoop如何诞生3、Hadoop主要发展历程 (二)Hadoop的优势1、扩容能力强2、成本低3、高效率4、可靠性5、高容错性 (三…...
Flutter笔记:发布一个电商中文货币显示插件Money Display
Flutter笔记 电商中文货币显示插件 Money Display 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/1338…...
解密zkLogin:探索前沿的Sui身份验证解决方案
由于钱包复杂性导致的新用户入门障碍是区块链中一个长期存在的问题,而zkLogin是其简单的解决方案。通过使用前沿的密码学和技术,zkLogin既优雅又复杂。本文深入探讨了zkLogin的工作原理,涵盖了用户和开发者的安全性方面,并解释了S…...
js构造函数
构造函数 通过 new 函数名 来实例化对象的函数叫构造函数。 任何的函数都可以作为构造函数存在。之所以有构造函数与普通函数之分,主要从功能上进行区别的,构造函数的主要 功能为 初始化对象,特点是和new 一起使用。new就是在创建对象&#x…...
性能测试-redis常见问题
缓存击穿、缓存穿透、缓存雪崩 缓存雪崩 解决办法 1.设置缓存失效时间,不要在同一时间 2.redis集群部署 3.不设置缓存设置时间 4.定时刷缓存的时间 缓存穿透 请求不管返回什么数据都返回给redis对参数合法器进行验证,不合法的时候直接过滤掉使用布…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
