基于SpringBoot实现WebSocket实时通讯的服务端和客户端

news2024/11/16 1:17:09

实现功能

服务端注册的客户端的列表;服务端向客户端发送广播消息;服务端向指定客户端发送消息;服务端向多个客户端发送消息;客户端给服务端发送消息;
效果:
请添加图片描述在这里插入图片描述

环境

jdk:1.8
SpringBoot:2.4.17

服务端

1.引入依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2.在启动类上加上开启WebSocket的注解

@EnableWebSocket

3.配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * date created : Created in 2024/3/18 16:57
 * description  : WebSocketConfig 主要解决使用了@ServerEndpoint注解的websocket endpoint不被springboot扫描到的问题
 * class name   : WebSocketConfig
 */
@Configuration
public class WebSocketConfig {

    /**
     * 注入ServerEndpointExporter,
     * 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }


}

4.服务端实现

/**
 * date created : Created in 2024/3/18 16:31
 * description  : 服务端实现,方法的封装
 * class name   : WebSocketServer
 */
@Component
@Slf4j
@ServerEndpoint("/websocket/{applicationName}")
public class WebSocketServer {
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    // 应用名称
    private String applicationName;

    //虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
    private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();
    // 用来存在线连接用户信息
    private static final ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();


    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "applicationName") String applicationName) {
        try {
            this.session = session;
            this.applicationName = applicationName;
            webSockets.add(this);
            sessionPool.put(applicationName, session);
            log.info("【websocket消息】有新的连接,总数为:" + webSockets.size());
            log.info("【当前客户端列表】:"+ sessionPool.keySet());
        } catch (Exception e) {
        }
    }

    /**
     * description : 有连接断开之后的处理方法
     * method name : onClose
     * param       : []
     * return      : void
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);
            sessionPool.remove(this.applicationName);
            log.info("【websocket消息】连接断开,总数为:" + webSockets.size());
            log.info("【当前客户端列表】:"+ sessionPool.keySet());
        } catch (Exception e) {
        }
    }

    /**
     * description : 收到客户端消息的处理方法
     * method name : onMessage
     * param       : [message]
     * return      : void
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端消息:" + message);
    }

    /**
     * description : 错误处理
     * method name : onError
     * param       : [session, error]
     * return      : void
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误,原因:" + error.getMessage());
        error.printStackTrace();
    }


    /**
     * description : 广播消息 给所有注册的客户端发送消息
     * method name : sendBroadcastMessage
     * param       : [message]
     * return      : void
     */
    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) {
                e.printStackTrace();
            }
        }
    }

    /**
     * description : 给指定的客户端发送消息
     * method name : sendApplicationMessage
     * param       : [applicationName 客户端的应用名称, message 要发送的消息]
     * return      : void
     */
    public void sendApplicationMessage(String applicationName, String message) {
        Session session = sessionPool.get(applicationName);
        if (session != null && session.isOpen()) {
            try {
                log.info("【websocket消息】 单点消息:" + message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * description : 给多个客户端发送消息
     * method name : sendMassApplicationMessage
     * param       : [applicationNames 注册的客户端的应用名称, message 要发送的消息]
     * return      : void
     */
    public void sendMassApplicationMessage(String[] applicationNames, String message) {
        for (String userId : applicationNames) {
            Session session = sessionPool.get(userId);
            if (session != null && session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:" + message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

客户端

1.客户端配置

yaml文件的末尾添加

# websocket的配置
websocket:
  host: localhost
  port: 19022
  prefix: websocket

2.客户端配置类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * date created : Created in 2024/3/19 14:36
 * description  : 注入配置文件中的参数 并生成服务端的对应的url
 * class name   : WebSocketProperties
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "websocket")
@Configuration
public class WebSocketProperties {
    @Value("${spring.application.name}")
    String appName;
    String host;
    String port;
    String prefix;
    public String getUrl() {
        return String.format("ws://%s:%s/%s/%s", host, port, prefix,appName);
    }
}

3.客户端实现

import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

import javax.websocket.ClientEndpoint;
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * date created : Created in 2024/3/18 16:36
 * description  : 客户端接收服务端的实时消息、发送消息等方法的封装
 * class name   : WebSocketClient
 */


@ClientEndpoint
@AutoConfigureBefore(WebSocketProperties.class)
@Component
@Import(WebSocketProperties.class)
@Configuration
public class WebSocketClient {


    private Session session;

    public WebSocketClient() {
        try {
            WebSocketProperties webSocketProperties = SpringUtils.getBean(WebSocketProperties.class);
            WebSocketContainer container = ContainerProvider.getWebSocketContainer();
            container.connectToServer(this, new URI(webSocketProperties.getUrl()));
        } catch (DeploymentException | URISyntaxException | IOException e) {
            e.printStackTrace();
        }
    }

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        System.out.println("Connected to server");
    }

    @OnMessage
    public String onMessage(String message) {
        System.out.println("来自WebSocket的消息: " + message);
        return message;
    }

    @OnClose
    public void onClose() {
        System.out.println("Disconnected from server");
    }

    public void register() {
        try {
            session.getBasicRemote().sendText("register");
            System.out.println("Registered with server");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void unregister() {
        try {
            session.getBasicRemote().sendText("unregister");
            System.out.println("Unregistered from server");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



}

使用@Autowired注入配置类无法注入,使用工具类获取,工具类:

* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtils implements ApplicationContextAware {
	private static ApplicationContext context;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		context = applicationContext;
	}

	public static Object getBean(String name) {
		return context.getBean(name);
	}

	public static <T> T getBean(Class<T> clazz) {
		return context.getBean(clazz);
	}

	public static <T> T getBean(String name, Class<T> clazz) {
		return context.getBean(name, clazz);
	}
}

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

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

相关文章

社区热议!54.8k Star开源项目,GPT-4Free : 让GPT4免费不是梦

Hello&#xff0c;我是Aitrainee&#xff0c;GPT4Free就是最近传得沸沸扬扬的那个GPT4项目。大家都知道&#xff0c;虽然ChatGPT是免费的&#xff0c;但如果你想用到那些功能更强大的大模型&#xff0c;比如GPT-4、gemini-pro、claude&#xff0c;那就只能选择付费了。 但现在&…

在Linux搭建Emlog博客结合内网穿透实现公网访问本地个人网站

文章目录 前言1. 网站搭建1.1 Emolog网页下载和安装1.2 网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总结 前言 博客作为使…

【2024最新版,redis7】redis底层的10种数据结构

前言&#xff1a;本文redis版本&#xff1a;7.2.4 本文语雀原文地址&#xff08;首发更新&#xff09;&#xff1a;https://www.yuque.com/wzzz/redis/xg2cp37kx1s4726y 本文CSDN转载地址&#xff1a; https://blog.csdn.net/u013625306/article/details/136842107 1. 常见的数…

烯冷新能源邀您参观2024长三角快递物流展

参加企业介绍 宁波戈雷贝拓科技有限公司&#xff08;宁波烯冷新能源科技有限公司&#xff09;宁波烯冷新能源科技有限公司于2022年初成立&#xff0c;依托中国科学院宁波材料技术与工程研究所和国家石墨烯创新中心&#xff0c;公司开发产品包括&#xff1a;新能源制冷系统和集…

Mysql学习--深入探究索引和事务的重点要点与考点

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

一键入门Ubuntu22!

目录 一、安装 二、常用目录 三、常用指令 四、用户指令 五、ssh与scp 六、服务相关 七、Python与Pycharm 八、Vim编辑器 九、Ubuntu22下使用Mysql 十、Ubuntu22下使用mongodb 十一、Ubuntu22下使用redis Ubuntu是一个基于Debian的开源操作系统&#xff0c;由Canoni…

LeetCode每日一题——x 的平方根

x 的平方根OJ链接&#xff1a;69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路&#xff1a; 乍一看题目只需要算一个数的平方根&#xff0c;根据我们之前学的C语言我们能很快的想到使用sqrt&#xff0c;pow这类的<math.h>库函数&#xf…

【计算机网络篇】数据链路层(2)封装成帧和透明传输

文章目录 &#x1f95a;封装成帧和透明传输&#x1f388;封装成帧&#x1f388;透明传输&#x1f5d2;️面向字节的物理链路使用字节填充的方法实现透明传输。&#x1f5d2;️面向比特的物理链路使用比特填充的方法实现透明传输。 &#x1f6f8;练习 &#x1f95a;封装成帧和透…

【目标检测基础篇】目标检测评价指标:mAP计算的超详细举例分析以及coco数据集标准详解(AP/AP50/APsmall.....))

学习视频&#xff1a; 霹雳吧啦Wz-目标检测mAP计算以及coco评价标准 【目标检测】指标介绍&#xff1a;mAP 1 TP/FP/FN TP(True Positive) : IoU>0.5的检测框数量(同一Ground truth只计算一次)FP(False Positive) : IoU<0.5的检测框(或者是检测到同一个GT的多余检测框的…

(css)vue 自定义背景 can‘t resolve

(css)vue 自定义背景 can’t resolve 旧写法&#xff1a; background-image: url(/assets/images/step-bg.jpg);background-size: 100% 100%; 新写法&#xff1a; background-image: url(~/assets/images/step-bg.jpg);background-size: 100% 100%; 解决参考&#xff1a;https…

印刷企业实施MES管理系统如何做好需求分析

在数字化、信息化的大潮中&#xff0c;印刷企业面临着转型升级的迫切需求。MES管理系统作为连接企业资源计划ERP和现场自动化系统的桥梁&#xff0c;对于提升印刷企业的生产效率、优化资源配置、提高产品质量具有重要意义。因此&#xff0c;做好MES管理系统的需求分析&#xff…

分布式搜索引擎elasticsearch专栏二

上一篇的传送门&#xff1a; 分布式搜索引擎elasticsearch专栏一-CSDN博客 这一篇博文主要讲解elasticsearch的数据搜索功能。下面会分别使用DSL和RestClient实现搜索。 1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsea…

基于ssm的勤工助学管理系统+数据库+报告+免费远程调试

项目介绍: 基于ssm的勤工助学管理系统。Javaee项目&#xff0c;ssm项目。采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringMvc Mybatisplus VuelayuiMaven来实现。有管理员和老…

​酒店小程序开发的功能与优势解析

随着科技的快速发展和移动互联网的普及&#xff0c;越来越多的服务行业开始尝试利用小程序来提供便捷的服务。对于酒店业来说&#xff0c;开发一个酒店小程序不仅可以提升用户体验&#xff0c;还有助于提高运营效率。本文将详细介绍酒店小程序的开发功能以及它的优势。 一、酒…

Echarts 利用多X轴实现未来15天天气预报

Echarts 利用多X轴实现未来15天天气预报 UI 设计图 Echarts 实现效果 代码实现 代码分解 echarts 图表上下均显示数据 通过设置 grid.top 和 grid.bottom 设置白天和夜间天气展示区域 grid: {top: 36%,bottom: 36%,left: 5%,right: 5%}, 天气图标的设置 由于 axisLabel 的…

Redis中AOF、RDB和复制功能对过期键的处理

AOF、RDB和复制功能对过期键的处理 生成RDB文件 在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时&#xff0c;程序会对数据库中的键进行检查&#xff0c;已过期的键不会被保存到新创建的RDB文件中。 例子 举个例子&#xff0c;如果数据库中包含三个键k1、k2、k3&#x…

六种GPU虚拟化:除了直通、全虚拟化 (vGPU)还有谁?

在大类上计算虚拟化技术有这3种&#xff1a; 软件模拟、直通独占(如网卡独占、显卡独占)、直通共享&#xff08;如vCPU 、vGPU&#xff09;。但对于显卡GPU而言我总结细化出至少这6种分类&#xff1a; 第一种、软件模拟&#xff08;eg sGPU&#xff09;, 又叫半虚拟化。第二种…

活用 C语言之union的精妙之用

一、union的基本定义 Union的中文叫法又被称为共用体、联合或者联合体。它的定义方式与结构体相同,但意义却与结构体完全不同。下面是union的定义格式: union 共用体名 {成员列表}共用体变量名;它与结构体的定义方式相同,但区别在于共用体中的成员的起始地址都是相同的,…

Python环境下基于1D-CNN的轴承故障诊断及TSNE特征可视化

1D CNN 处理一维信号具有显著优势&#xff0c;已在很多领域得到初步应用&#xff1a; 心电图监测&#xff1a;将1DCNN应用于心脏病监测&#xff0c;其方法是针对每一个心脏病人的&#xff0c;即对于每个心律失常患者使用该患者特有的训练数据&#xff0c;专门训练出一个紧凑的…

仿牛客项目Day11 Redis

Redis概念 Redis是一种Nosql非关系型数据库&#xff0c;以键值对的形式存储数据 Redis中的所有数据都存在内存中&#xff0c;访问速度很快。同时&#xff0c;Redis还可以将内存中的数据以快照或日志的形式存到硬盘里&#xff0c;以保证数据的安全性 Redis要到github上去下载…