springboot实现webSocket服务端和客户端demo
1:pom导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.2.7.RELEASE</version></dependency>
2:myWebSocketClient自定义webSocket客户端
package com.example.poi.utils;import org.springframework.stereotype.Component;
import javax.websocket.*;
import java.io.IOException;/*** @Author xu* @create 2023/9/11 18*/
@ClientEndpoint
@Component
public class MyWebSocketClient {public Session session;@OnOpenpublic void onOpen(Session session) {this.session = session;System.out.println("WebSocket2连接已打开");}@OnMessagepublic void onMessage(String message) {System.out.println("收到消息2:" + message);}@OnClosepublic void onClose() {System.out.println("客户端关闭2");}@OnErrorpublic void onError(Throwable throwable) {System.err.println("发生错误2:" + throwable.getMessage());}public void sendMessage(String message) throws IOException {session.getBasicRemote().sendText(message);}
}
3:WebSocketServer自定义webSocket服务端
package com.example.poi.utils;import cn.hutool.json.JSONObject;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;/*** @Author xu* @create 2023/7/21 19*/@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {static Log log = LogFactory.get(WebSocketServer.class);//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。private static int onlineCount = 0;//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();//与某个客户端的连接会话,需要通过它来给客户端发送数据public Session session;//接收sidprivate String sid = "";/*** 连接建立成功调用的方法** @param session* @param sid*/@OnOpenpublic void onOpen(Session session, @PathParam("sid") String sid) {this.session = session;webSocketSet.add(this); //加入set中addOnlineCount(); //在线数加1log.info("有新窗口开始监听:" + sid + ",当前在线人数为" + getOnlineCount());this.sid = sid;/*try {sendMessage("连接成功");} catch (IOException e) {log.error("websocket IO异常");}*/}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {webSocketSet.remove(this); //从set中删除subOnlineCount(); //在线数减1log.info("有一连接关闭!当前在线人数为" + getOnlineCount());}/*** 收到客户端消息后调用的方法* 客户端发送过来的消息** @param message* @param session*/@OnMessagepublic void onMessage(String message, Session session) {log.info("收到来自窗口" + sid + "的信息:" + message);//群发消息for (WebSocketServer item : webSocketSet) {if (ObjectUtils.equals(item.sid, sid)) {try {JSONObject jsonObject = new JSONObject();jsonObject.put("name", sid);item.sendMessage(jsonObject.toString());} catch (IOException e) {e.printStackTrace();}}}}/*** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("发生错误");error.printStackTrace();}/*** 实现服务器主动推送** @param message* @throws IOException*/public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText(message);}/*** 获取存在的webSocket*/public CopyOnWriteArraySet<WebSocketServer> getWebSocketServer() {return webSocketSet;}public String getSid(){return sid;}public void close2(String ss){for (WebSocketServer webSocketServer : webSocketSet) {if (webSocketServer.sid.equals(ss)) {webSocketSet.remove(webSocketServer);log.info("删除了:"+ss);}}subOnlineCount(); //在线数减1log.info("有一连接关闭!当前在线人数为" + getOnlineCount());}/*** 发送消息** @param message* @param sid* @throws IOException*/public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {log.info("推送消息到窗口" + sid + ",推送内容:" + message);for (WebSocketServer item : webSocketSet) {try {//这里可以设定只推送给这个sid的,为null则全部推送if (sid == null) {item.sendMessage(message);} else if (item.sid.equals(sid)) {item.sendMessage(message);}} catch (IOException e) {continue;}}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocketServer.onlineCount++;}public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}/*** 必须要有这个bean才能生效使用webSocketServer*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
4:controller控制层
@SneakyThrows@GetMapping("/testSql")public List<EntityDemo> testSql(String id) {/** WebSocket服务器的地址*/try {Random random = new Random();Integer i = random.nextInt(5) + 1;CopyOnWriteArraySet<WebSocketServer> webSocketServerSet = webSocketServer.getWebSocketServer();for (WebSocketServer socketServer : webSocketServerSet) {if (socketServer.getSid().equals(i.toString())) {webSocketServer.close2(i.toString());return null;}}URI uri = new URI("ws://127.0.0.1:9088/test/websocket/" + i);WebSocketContainer container = ContainerProvider.getWebSocketContainer();container.connectToServer(myWebSocketClient, uri);myWebSocketClient.sendMessage("你好" + i);} catch (Exception e) {throw new RuntimeException(e);}log.info("++++++");return null;}
5:注意事项:连接自动断开
- webSocket连接之后,发现一个问题:就是每隔一段时间如果不传送数据的话,与前端的连接就会自动断开。采用心跳消息的方式,就可以解决这个问题。比如客服端每隔30秒自动发送ping消息给服务端,服务端返回pong
5-1:注意事项:对象无法自动注入
- 使用了@ServerEndpoint注解的类中使用@Resource或@Autowired注入对象都会失败,并且报空指针异常,解决方法:如下
- @ServerEndpoint注解的类,使用静态对象,并且对外暴露set方法,这样在对象初始化的时候,将其注入到WebSocketServer
@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {private static EntityDemoServiceImpl entityDemoService;public static void setEntityDemoService(EntityDemoServiceImpl entityDemoService) {WebSocketServer.entityDemoService = entityDemoService;}
- 在要注入到@ServerEndpoint注解的类中,使用@PostConstruct后置注入
@Service
public class EntityDemoServiceImpl extends ServiceImpl<EntityDemoMapper, EntityDemo> implements IEntityDemoService {@PostConstructpublic void init() {WebSocketServer.setEntityDemoService(this);}
}
5-2:注意事项:Session无法被序列化
- 分布式场景会存在这样一个问题,当一次请求负载到第一台服务器时,session在第一台服务器线程上,第二次请求,负载到第二台服务器上,此时通过userId查找当前用户的session时,是查找不到的。
本来想着把session存入到redis中,就可以从redis获取用户的session,希望用这种方式来解决分布式场景下消息发送的问题。这种场景可以通过发送消息中间件的方式解决。具体这样解决:每次连接时,都将userId和对应的session存入到本机,发送消息时,直接发送给MQ-Broker,然后每台应用负载都去消费这个消息,拿到消息之后,判断在本机能根据userId是否能找到session,找到则推送到客户端
相关文章:
springboot实现webSocket服务端和客户端demo
1:pom导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.2.7.RELEASE</version></dependency>2:myWebSocketClien…...
代码走读: FFMPEG-ffplayer02
AVFrame int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame) 选取一个音频解码器 和 一个视频解码器分别介绍该解码器功能 音频G722 g722dec.c -> g722_decode_frame 通过 ff_get_buffer 给 传入的 frame 指针分配内存 g722_decode_…...

【数据结构】——排序算法的相关习题
目录 一、选择题题型一 (插入排序)1、直接插入排序2、折半插入排序3、希尔排序 题型二(交换排序)1、冒泡排序2、快速排序 题型三(选择排序)1、简单选择排序~2、堆排序 ~题型四(归并排序…...

C高级day5(Makefile)
一、Xmind整理: 二、上课笔记整理: 1.#----->把带参宏的参数替换成字符串 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX(a,b) a>b?a:b #define STR(n) #n int main(int argc, const char *argv…...

Android 系统中适配OAID获取
一、OAID概念 OAID(Open Anonymous Identification)是一种匿名身份识别标识符, 用于在移动设备上进行广告追踪和个性化广告投放。它是由中国移动通信集 团、中国电信集团和中国联通集团共同推出的一项行业标准 OAID值为一个64位的数字 二、…...
差分数组leetcode 2770 数组的最大美丽值
什么是差分数组 差分数组是一种数据结构,它存储的是一个数组每个相邻元素的差值。换句话说,给定一个数组arr[],其对应的差分数组diff[]将满足: diff[i] arr[i1] - arr[i] 对于所有 0 < i < n-1 差分数组的作用 用于高效…...
请求响应状态码
请求与响应&状态码 Requests部分 请求行、消息报头、请求正文。 Header解释示例Accept指定客户端能够接收的内容类型Accept: text/plain, text/htmlAccept-Chars et浏览器可以接受的字符编码集。Accept-Charset: iso-8859-5Accept-Encodi ng指定浏览器可以支持的web服务…...

安卓机型系统美化 Color.xml文件必备常识 自定义颜色资源
color.xml文件是Android工程中用来进行颜色资源管理的文件.可以在color.xml文件中通过<color>标签来定义颜色资源.我们在布局文件中、代码中、style定义中或者其他资源文件中,都可以引用之前在color.xml文件中定义的颜色资源。 将color.xml文件拷到res/value…...

YOLO物体检测-系列教程1:YOLOV1整体解读(预选框/置信度/分类任/回归任务/损失函数/公式解析/置信度/非极大值抑制)
🎈🎈🎈YOLO 系列教程 总目录 YOLOV1整体解读 YOLOV2整体解读 YOLOV1提出论文:You Only Look Once: Unified, Real-Time Object Detection 1、物体检测经典方法 two-stage(两阶段):Faster-rc…...

2023/9/12 -- C++/QT
作业 实现一个图形类(Shape),包含受保护成员属性:周长、面积, 公共成员函数:特殊成员函数书写 定义一个圆形类(Circle),继承自图形类,包含私有属性…...

【Purple Pi OH RK3566鸿蒙开发板】OpenHarmony音频播放应用,真实体验感爆棚!
本文转载于Purple Pi OH开发爱好者,作者ITMING 。 原文链接:https://bbs.elecfans.com/jishu_2376383_1_1.html 01注意事项 DevEco Studio 4.0 Beta2(Build Version: 4.0.0.400) OpenHarmony SDK API 9 创建工程类型选择Appli…...
Android rom开发:9.0系统上实现4G wifi 以太网共存
framework层修改网络优先级,4G > wifi > eth 修改patch如下: diff --git a/frameworks/base/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/frameworks/base/services/core/java/com/android/server/connectivit…...
高速自动驾驶HMI人机交互
概述 目的 本文档的目的是描述高速自动驾驶功能涉及的HMI显示需求技术规范和设计说明。 范围 术语及缩写 设计与实验标准 设计标准 设计标准-非法规类设计标准-法规类 HMI交互需求 CL4功能界面 HMI显示器[伊1] 中应包含CL4功能设置界面,提供给用户进行设置操作或显…...
【自然语言处理】关系抽取 —— SOLS 讲解
SOLS 论文信息 标题:Speaker-Oriented Latent Structures for Dialogue-Based Relation Extraction 作者:Guoshun Nan, Guoqing Luo, Sicong Leng, Yao Xiao, Wei Lu 发布时间与更新时间:2021.09.11 主题:自然语言处理、关系抽取、对话场景、跨语句、DialogRE、GCN arXiv:…...

周易算卦流程c++实现
代码 #include<iostream> using namespace std; #include<vector> #include<cstdlib> #include<ctime> #include<Windows.h>int huaYiXiangLiang(int all, int& left) {Sleep(3000);srand(time(0));left rand() % all 1;while (true) {if…...

软件架构设计(十三) 构件与中间件技术
中间件的定义 其实中间件是属于构件的一种。是一种独立的系统软件或服务程序,可以帮助分布式应用软件在不同技术之间共享资源。 我们把它定性为一类系统软件,比如我们常说的消息中间件,数据库中间件等等都是中间件的一种体现。一般情况都是给应用系统提供服务,而不是直接…...

PyTorch深度学习实战——基于ResNet模型实现猫狗分类
PyTorch深度学习实战——基于ResNet模型实现猫狗分类 0. 前言1. ResNet 架构2. 基于预训练 ResNet 模型实现猫狗分类相关链接 0. 前言 从 VGG11 到 VGG19,不同之处仅在于网络层数,一般来说,神经网络越深,它的准确率就越高。但并非…...

机器学习第六课--朴素贝叶斯
朴素贝叶斯广泛地应用在文本分类任务中,其中最为经典的场景为垃圾文本分类(如垃圾邮件分类:给定一个邮件,把它自动分类为垃圾或者正常邮件)。这个任务本身是属于文本分析任务,因为对应的数据均为文本类型,所以对于此类任务我们首先…...

基于Java+SpringBoot+Vue的图书借还小程序的设计与实现(亮点:多角色、点赞评论、借书还书、在线支付)
图书借还管理小程序 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序(小蔡coding)2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述 五、系统实现5.1 小…...

【校招VIP】前端计算机网络之UDP相关
考点介绍 UDP是一个简单的面向消息的传输层协议,尽管UDP提供标头和有效负载的完整性验证(通过校验和),但它不保证向上层协议提供消息传递,并且UDP层在发送后不会保留UDP 消息的状态。因此,UDP有时被称为不可…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...