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,找到则推送到客户端

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/77236.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nlp系列(7)实体识别(Bert)pytorch

模型介绍 本项目是使用Bert模型来进行文本的实体识别。 Bert模型介绍可以查看这篇文章&#xff1a;nlp系列&#xff08;2&#xff09;文本分类&#xff08;Bert&#xff09;pytorch_bert文本分类_牧子川的博客-CSDN博客 模型结构 Bert模型的模型结构&#xff1a; 数据介绍 …

骨传导耳机的危害有哪些?会损害听力吗?

如果正常的使用&#xff0c;骨传导耳机是没有危害的&#xff0c;由于骨传导耳机独特的传声方式&#xff0c;所以并不会对人体造成损伤&#xff0c;还可以在一定程度上保护听力。 如果想更具体知道骨传导耳机有什么危害&#xff0c;就要先了解什么是骨传导耳机&#xff0c;骨传…

小程序自定义tabbar

前言 使用小程序默认的tabbar可以满足常规开发&#xff0c;但是满足不了个性化需求&#xff0c;如果想个性化开发就需要用到自定义tabbar,以下图为例子 一、在app.json配置 先按照以往默认的形式配置&#xff0c;如果中间的样式特殊则不需要配置 "tabBar": {&qu…

来可LCWLAN-600P产品使用和常见问题说明

01LCWLAN-600P简介 LCWLAN-600P是来可电子最新生产的一款CAN转WiFi设备&#xff0c;该设备的主要功能是将CAN数据转换成网络数据并通过无线网络转发出去。设备支持8~30V宽压供电&#xff0c;出厂默认配置为AP模式&#xff0c;设备供电后可在电脑的WiFi搜索栏搜索到名称为LCWLA…

【计算机网络】网络编程接口 Socket API 解读(3)

Socket 是网络协议栈暴露给编程人员的 API&#xff0c;相比复杂的计算机网络协议&#xff0c;API 对关键操作和配置数据进行了抽象&#xff0c;简化了程序编程。 本文讲述的 socket 内容源自 Linux 发行版 centos 9 上的 man 工具&#xff0c;和其他平台&#xff08;比如 os-x …

【Linux-Day10-信号量,共享内存,消息队列】

信号量 信号量描述 信号量是一个特殊的变量&#xff0c;一般取正数值。它的值代表允许访问的资源数目&#xff0c;获取资源 时&#xff0c;需要对信号量的值进行原子减一&#xff0c;该操作被称为 P 操作。 当信号量值为 0 时&#xff0c;代表没有资源可用&#xff0c;P 操作…

2022年全国研究生数学建模竞赛华为杯B题方形件组批优化问题求解全过程文档及程序

2022年全国研究生数学建模竞赛华为杯 B题 方形件组批优化问题 原题再现&#xff1a; 背景介绍   智能制造被“中国制造2025”列为主攻方向, 而个性化定制、更短的产品及系统生命周期、互联互通的服务模式等成为目前企业在智能制造转型中的主要竞争点。以离散行业中的产品为…

20230912java面经整理

1.gc算法有哪些 引用计数&#xff08;循环引用&#xff09;和可达性分析找到无用的对象 标记-清除&#xff1a;简单&#xff0c;内存碎片&#xff0c;大对象找不到空间 标记-复制&#xff1a;分成两半&#xff0c;清理一半&#xff0c;没有碎片&#xff0c;如果存活多效率低&a…

Python之离线安装第三方库

1、场景介绍 在一些服务器上&#xff0c;我们搭建完Python环境之后&#xff0c;因为服务器的网络限制原因&#xff0c;不能直接通过pip命令下载安装Python的依赖包。 因此&#xff0c;我们需要在可以正常上网的服务器上下载好所需的依赖包文件&#xff0c;然后拷贝到目标服务器…

嘉泰实业:真实低门槛,安全有保障

在互联网金融大行其道的当下&#xff0c;无论用户是多么的青睐、喜爱这种便捷的理财方式&#xff0c;也一定得把资金安全放在心上。要投就投那些实力背景雄厚&#xff0c;诚信经营的平台&#xff0c;可以选择投资用户基数庞大的理财老品牌&#xff0c;也可以选择发展势头迅猛的…

OneFormer: One Transformer to Rule Universal Image Segmentation论文笔记

论文https://arxiv.org/pdf/2211.06220.pdfCodehttps://github.com/SHI-Labs/OneFormer 文章目录 1. Motivation2. 方法2.1 与Mask2Former的相同之处2.2 OneFormer创新之处2.3 Task Conditioned Joint Training2.4 Query Representations2.4 Task Guided Contrastive Queries 3…

备忘录模式简介

概念&#xff1a; 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许在不暴露对象内部状态的情况下捕获和恢复对象之前的状态。该模式通过将对象的状态封装到备忘录中&#xff0c;并将其保存在一个管理者类中&#xff0c;从而实现了对…

【Redis】为什么要学 Redis

文章目录 前言一、Redis 为什么快二、Redis 的特性2.1 将数据储存到内存中2.2 可编程性2.3 可扩展性2.4 持久性2.5 支持集群2.6 高可用性 三、Redis 的应用场景四、不能使用 Redis 的场景 前言 关于为什么要学 Redis 这个问题&#xff0c;一个字就可以回答&#xff0c;那就是&…

学习记忆——宫殿篇——记忆宫殿——数字编码——记忆数字知识点

面对错综复杂的数字信息&#xff0c;我们想要记住可以通过以下三点&#xff1a; 1、首先找到关键词 2、数字编码牢记 3、关键词跟编码链接 案例&#xff1a;会计考试-时间期限为 3、7、10 日、1 年的知识点 3 天 (1)托收承付的承付期验单付款为 3 天。 (2)失票人应当在通…

帝国cms后台访问链接提示“非法来源”解决方法

提示“非法来源”的原因 帝国CMS更新升级7.2后,新增了后台安全模式,后台推出了金刚模式来验证链接来源。后台所有链接都需要登录后才能访问,直接强制访问后台页面链接都会提示“非法来源”。不是正常登录后台的用户无法直接访问到内容,保证了后台数据安全。 那么我们在日常…

【设计模式】三、概述分类+单例模式

文章目录 概述设计模式类型 单例模式饿汉式&#xff08;静态常量&#xff09;饿汉式&#xff08;静态代码块&#xff09;懒汉式(线程不安全)懒汉式(线程安全&#xff0c;同步方法)懒汉式(线程安全&#xff0c;同步代码块)双重检查静态内部类枚举单例模式在 JDK 应用的源码分析 …

SpringMVC实现文件上传和下载功能

文件下载 ResponseEntity用于控制器方法的返回值类型&#xff0c;该控制器方法的返回值就是响应到浏览器的响应报文。具体步骤如下&#xff1a; 获取下载文件的位置&#xff1b;创建流&#xff0c;读取文件&#xff1b;设置响应信息&#xff0c;包括响应头&#xff0c;响应体以…

04-Redis哨兵高可用架构

上一篇&#xff1a;03-Redis主从架构 架构说明 sentinel哨兵是特殊的redis服务&#xff0c;不提供读写服务&#xff0c;主要用来监控redis实例节点。 哨兵架构下client端第一次从哨兵找出redis的主节点&#xff0c;后续就直接访问redis的主节点&#xff0c;不会每次都通过s…

【2023最新B站评论爬虫】用python爬取上千条哔哩哔哩评论

文章目录 一、爬取目标二、展示爬取结果三、爬虫代码四、同步视频五、附完整源码 您好&#xff0c;我是 马哥python说&#xff0c;一枚10年程序猿。 一、爬取目标 之前&#xff0c;我分享过一些B站的爬虫&#xff1a; 【Python爬虫案例】用Python爬取李子柒B站视频数据 【Pyt…

Java实现合并多个excel操作

涉及较多封装的工具类&#xff0c;所有依赖的工具类均提供代码&#xff0c;根据名称新建对应的类&#xff0c;在每个工具类中再引入相应的依赖即可 首先需要明确的是&#xff0c;需要合并的每个excel的表头名称必须是相同的&#xff0c; 针对表头&#xff0c;建立传输的dto&a…