SpringBoot3整合WebSocket

news2025/7/18 21:39:21

一、WebSocket简介

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信,允许服务器主动向客户端推送数据。
与传统的 HTTP 请求-响应模式不同,WebSocket 在建立连接后,允许服务器和客户端之间进行双向实时通信。

WebSocket主要特点:

  • 建立在 TCP 协议之上。
  • 与 HTTP 协议有着良好的兼容性。
  • 数据格式比较轻量,性能开销小,通信高效。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务器通信。
  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

二、SpringBoot3集成WebSocket

引入 WebSocket依赖:

        <!-- WebSocket依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>3.4.5</version>
        </dependency>

1、WebSocket配置类

创建 WebSocket 配置类,启用 WebSocket 功能并注册端点。

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private CustomTextWebSocketHandler customTextWebSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        /**
         * addHandler方法参数说明
         * 参数1:添加 WebSocket 处理器
         * 参数2:WebSocket的连接路径、端口为项目端口、路径为自定义。比如:URL = ws://127.0.0.1:38081/ws
         */
        registry.addHandler(customTextWebSocketHandler, "/ws")
                .setAllowedOrigins("*"); // 允许跨域访问
    }

}

2、自定义WebSocket处理器

创建自定义的 WebSocket 处理器,处理消息收发。

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomTextWebSocketHandler extends TextWebSocketHandler {

    private final WebSocketSessionManager sessionManager;

    /**
     * 用于存储WebSocket会话
     */
    private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String sessionId = session.getId();
        sessionManager.add(session);
        log.info("WebSocket连接建立成功:{}", sessionId);
    }


    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        log.info("WebSocket收到消息 ==> sessionId = {}, payload = {} ", session.getId(), message.getPayload());

        boolean open = session.isOpen();
        if (!open) {
            log.info("WebSocket发送消息失败 ==> sessionId = {} 不在线 ", session.getId());
            return;
        }

        try {
            /**
             * TODO 发送回复消息
             */
            String replyMessage = "服务器 -> 客户端 收到了,消息" + payload;
            session.sendMessage(new TextMessage(replyMessage));
        } catch (IOException e) {
            log.info("WebSocket发送消息异常 ==> sessionId = {}, e = ", session.getId(), e);
        }

    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.info("WebSocket传输错误 ==> sessionId = {}, exception = {} ", session.getId(), exception.getMessage());
        // 传输错误,关闭连接
        this.afterConnectionClosed(session, CloseStatus.PROTOCOL_ERROR);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("WebSocket连接关闭 ==> sessionId = {}, CloseStatus = {} ", session.getId(), status);
        sessionManager.remove(session);
    }

    /**
     * 广播发送消息给所有连接的 WebSocket客户端
     *
     * @param message
     */
    public void sendMessageByBroadcast(String message) {
        sessionManager.getAllSession()
                .stream().filter(WebSocketSession::isOpen)
                .forEach(session -> {
                    try {
                        session.sendMessage(new TextMessage(message));
                    } catch (IOException e) {
                        log.info("WebSocket 广播发送消息异常 ==> sessionId = {}, e = ", session.getId(), e);
                    }
                });
    }

}

3、创建WebSocket会话管理类

自定义创建 WebSocket会话管理类,根据业务我们可以扩展存储不同的信息。

@Slf4j
@Component
public class WebSocketSessionManager {

    /**
     * 用于存储 WebSocket会话
     * key: sessionId
     * value: WebSocketSession
     */
    private final Map<String, WebSocketSession> wsSessionMap = new ConcurrentHashMap<>();

    /**
     * 添加 WebSocket会话
     *
     * @param session
     */
    public void add(WebSocketSession session) {
        wsSessionMap.put(session.getId(), session);
    }

    /**
     * 移除 WebSocket会话
     *
     * @param session
     */
    public void remove(WebSocketSession session) {
        wsSessionMap.remove(session.getId());
    }

    /**
     * 获取 WebSocket会话
     *
     * @param sessionId
     * @return
     */
    public WebSocketSession get(String sessionId) {
        return wsSessionMap.get(sessionId);
    }

    /**
     * 获取所有 WebSocket会话
     */
    public List<WebSocketSession> getAllSession() {
        Collection<WebSocketSession> sessions = wsSessionMap.values();
        return Optional.ofNullable(sessions).orElse(new ArrayList<>()).stream().toList();
    }
}

4、controller

创建测试controller,用来测试消息广播。

@RestController
@RequestMapping("/api/wsTest")
@RequiredArgsConstructor
public class WSTestController {

    private final CustomTextWebSocketHandler customTextWebSocketHandler;

    /**
     * 广播发送消息接口
     * 给所有连接的 WebSocket 客户端
     *
     * @param message
     * @return
     */
    @GetMapping("/sendMessageByBroadcast")
    public ResponseEntity<String> sendMessageByBroadcast(@RequestParam("message") String message) {
        customTextWebSocketHandler.sendMessageByBroadcast(message);
        return ResponseEntity.ok("广播发送消息接口 => 发送成功");
    }


}

启动项目,使用 WebSocket 在线测试工具等,测试ok。

WebSocket 在线测试工具:https://wstool.js.org/

在这里插入图片描述

根据业务还可以自定义握手拦截器校验token,配置最大闲置时间,比如3分钟没动自动关闭连接等配置。

在这里插入图片描述

– 求知若饥,虚心若愚。

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

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

相关文章

鸿蒙进阶——驱动框架UHDF 机制核心源码解读(一)

文章大纲 引言一、uhdf 概述二、uhdf 的核心参与角色1、drivers/hdf_core/adapter/uhdf2/manager/device_manager.c1.1、drivers/hdf_core/framework/core/manager/src/devmgr_service.c#DevmgrServiceGetInstance通过objectId获取IDevmgrService实例1.2、drivers/hdf_core/fra…

Idea 配合 devtools 依赖 实现热部署

核心依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency> yaml配置 spring: #…

从逻辑学视角严谨证明数据加密的数学方法与实践

文章目录 一、加密数据的数学指纹&#xff1a;信息论基础1.1 加密检测的核心原理1.2 香农熵&#xff1a;量化信息的不确定性 二、统计检验方法&#xff1a;从随机性到加密性2.1 卡方检验的数学原理2.2 游程检验与序列相关性2.3 NIST统计测试套件 三、加密算法的特征识别3.1 对称…

敦煌网测评从环境搭建到风控应对,精细化运营打造安全测评体系

自养号测评&#xff0c;抢占流量为快速提升产品权重和销量&#xff0c;很多卖家常采用自己养号补单测评的方式&#xff0c;技术搭建需要很多要素 一、硬件参数的关联性 在我们使用设备进行注册或操作账号的过程中&#xff0c;系统会记录下大量的系统与网络参数&#xff0c;其中…

本地分支git push 报错 fatal: The current branch XXXX has no upstream branch.

背景&#xff1a; 我新建了一个本地分支叫做 “新增Saas修改需求”&#xff0c;然后当我提交代码执行 git push时报错如下&#xff0c;并且代码仓库中没有我新建的“新增Saas修改需求”这个分支。 报错信息&#xff1a; 解决方法&#xff1a; 直接采用方法2 ”git push -u orig…

Python----循环神经网络(WordEmbedding词嵌入)

一、编码 当我们用数字来让电脑“认识”字符或单词时&#xff0c;最简单的方法是为每个字符或单词分配一个唯一的编号&#xff0c;然后用一个长长的向量来表示它。比如&#xff0c;假设“我”这个字在字典中的编号是第10个&#xff0c;那么它的表示就是一个很多0组成的向量&…

CUDA的设备,流处理器(Streams),核,线程块(threadblock),线程,网格(‌gridDim),块(block)和多gpu设备同步数据概念

CUDA的设备,流处理器&#xff0c;核&#xff0c;线程块&#xff08;threadblock&#xff09;&#xff0c;线程&#xff0c;网格&#xff08;‌gridDim&#xff09;&#xff0c;块&#xff08;block&#xff09;和多gpu设备同步数据概念 CUDA的设备,流处理器&#xff0c;核&…

LeetCode 1340. 跳跃游戏 V(困难)

题目描述 给你一个整数数组 arr 和一个整数 d 。每一步你可以从下标 i 跳到&#xff1a; i x &#xff0c;其中 i x < arr.length 且 0 < x < d 。i - x &#xff0c;其中 i - x > 0 且 0 < x < d 。 除此以外&#xff0c;你从下标 i 跳到下标 j 需要满…

x-cmd install | cargo-selector:优雅管理 Rust 项目二进制与示例,开发体验升级

目录 功能亮点安装优势特点适用场景总结 还在为 Rust 项目中众多的二进制文件和示例而烦恼吗&#xff1f;cargo-selector 让你告别繁琐的命令行&#xff0c;轻松选择并运行目标程序&#xff01; 功能亮点 交互式选择&#xff1a; 在终端中以交互方式浏览你的二进制文件和示例&…

大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战

大模型「瘦身」指南&#xff1a;从LLaMA到MobileBERT的轻量化部署实战 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 大模型「瘦身」指南&#xff1a;从LLaMA到MobileBERT的轻量化部署实战摘要引言一、轻量化技术…

从逻辑视角学习信息论:概念框架与实践指南

文章目录 一、信息论的逻辑基础与哲学内涵1.1 信息的逻辑本质&#xff1a;区分与差异1.2 逆范围原理与信息内容 二、信息论与逻辑学的概念交汇2.1 熵作为逻辑不确定性的度量2.2 互信息与逻辑依赖2.3 信道容量的逻辑极限 三、信息论的核心原理与逻辑基础3.1 最大熵原理的逻辑正当…

TDengine 运维—容量规划

概述 若计划使用 TDengine 搭建一个时序数据平台&#xff0c;须提前对计算资源、存储资源和网络资源进行详细规划&#xff0c;以确保满足业务场景的需求。通常 TDengine 会运行多个进程&#xff0c;包括 taosd、taosadapter、taoskeeper、taos-explorer 和 taosx。 在这些进程…

PPP 拨号失败:ATD*99***1# ... failed

从日志来看&#xff0c;主要有两类问题&#xff1a; 一、led_indicator_stop 报 invalid p_handle E (5750) led_indicator: …/led_indicator.c:461 (led_indicator_stop):invalid p_handle原因分析 led_indicator_stop() 的参数 p_handle &#xff08;即之前 led_indicator…

【计网】五六章习题测试

目录 1. (单选题, 3 分)某个网络所分配到的地址块为172.16.0.0/29&#xff0c;能接收目的地址为172.16.0.7的IP分组的最大主机数是&#xff08; &#xff09;。 2. (单选题, 3 分)若将某个“/19”的CIDR地址块划分为7个子块&#xff0c;则可能的最小子块中的可分配IP地址数量…

汇川EasyPLC MODBUS-RTU通信配置和编程实现

累积流量计算(MODBUS RTU通信数据处理)数据处理相关内容。 累积流量计算(MODBUS RTU通信数据处理)_流量积算仪modbus rtu通讯-CSDN博客文章浏览阅读219次。1、常用通信数据处理MODBUS通信系列之数据处理_modbus模拟的数据变化后会在原来的基础上累加是为什么-CSDN博客MODBUS通…

从 CANopen到 PROFINET:网关助力物流中心实现复杂的自动化升级

使用 CANopen PLC 扩展改造物流中心的传送带 倍讯科技profinet转CANopen网关BX-601-EIP将新的 PROFINET PLC 系统与旧的基于 CANopen 的传送带连接起来&#xff0c;简化了物流中心的自动化升级。 新建还是升级&#xff1f;这些问题通常出现在复杂的内部物流设施中&#xff0c;…

基于Yolov8+PyQT的老人摔倒识别系统源码

概述 ​​基于Yolov8PyQT的老人摔倒识别系统​​&#xff0c;该系统通过深度学习算法实时检测人体姿态&#xff0c;精准识别站立、摔倒中等3种状态&#xff0c;为家庭或养老机构提供及时预警功能。 主要内容 ​​完整可运行代码​​ 项目采用Yolov8目标检测框架结合PyQT5开发…

wsl2 不能联网

wsl2 安装后用 wifi 共享是能联网&#xff0c;问题出在公司网络限制 wsl2 IP 访问网络&#xff0c;但是主机可以上网。 解决办法&#xff0c;在主机用 nginx 设置代理&#xff0c;可能需要开端口权限 server {listen 9000;server_name localhost;location /ubuntu/ {#…

Java[IDEA]里的debug

目录 前言 Debug 使用Debug 总结 前言 这里我说一下就是 java IDEA 工具里的debug工具 里的一个小问题 就是 当我们使用debug去查看内部文档 查看不到 是为什么 Debug 所谓 debug 工具 他就是用来调试程序的 当我们写代码 报错 出错时 我们就可以使用这个工具 因此这个工具…

DAO模式

1. 持久化 简单来说&#xff0c;就是把代码的处理结果转换成需要的格式进行储存。 2. JDBC的封装 3. DAO模式 4. Properties类与Properties配置文件 添加 读取 5. 使用实体类传递数据 6. 总结 附录&#xff1a; BaseDao指南 BaseDao指南-CSDN博客