当前位置: 首页 > news >正文

【webSocket协议】进阶实战案例(Spring 原生低层 API)

       

目录

使用Spring 原生低层 API

1、引入依赖 

2、按需编写webSocket的拦截器

 3、编写消息处理器类

4、注册 WebSocket 处理器

使用低层 API的总结


        大家好,我是jstart千语。今天给大家带来webSocket在使用过程的实战案例,让大家明白webSocket在项目中是如何使用的,其实webSocket的实现方式有好几种,这里给大家介绍三种使用方式。分别是Spring 原生低层 API、 使用 @EnableWebSocketMessageBroker + ​STOMP 协议、使用JSR 356 标准。这一篇就先只讲Spring 原生低层 API的使用案例。


关于webSocket的原理等基础知识在上一篇有讲到: 【网络协议】WebSocket讲解-CSDN博客


使用Spring 原生低层 API

这种方式的使用步骤就是:

  1. 引入依赖:spring-boot-starter-websocket
  2. 按需编写webSocket的拦截器(与springMVC提供的拦截器不同)
  3. 编写消息处理器类
  4. 注册 WebSocket 处理器

1、引入依赖 

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


2、按需编写webSocket的拦截器

拦截器示例:

        握手前的方法形参中有一个attributes的map参数。这里就可以往里面塞一些需要进行业务处理的数据了,在后续可以通过session取出来。

@Component
public class exampleHandshakeInterceptor implements HandshakeInterceptor {
    
    //这里可以注入其他的service类,辅助业务的完成    

    @Resource
    private UserService userService;

    @Resource
    private PictureService pictureService;

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        //校验权限、、、
        
        return false; //false表示不放行、返回true表示放行
    }



    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {

    }
}


 3、编写消息处理器类

 让类继承一个父类TextWebSocketHandler ,其实更加原生的是WebSocketHandler,但我们最终要发的是文本消息嘛,所以就使用TextWebSocketHandler;

 然后重写里面的三个方法,分别表示建立连接时、客户端发送消息时、连接关闭时执行的方法

示例:项目背景为一个共同编辑一张图片的场景,但是同一时间只有一个用户能正在编辑,在同一张图片的编辑室内的其他用户可以同步看到被修改后的图片,当正在编辑的用户退出编辑到编辑室里时,编辑室内的其他用户才可以进入编辑。

可以看到下面的代码成员变量的map都是使用ConcurrentHashMap,这是一个线程安全的map,避免出现并发问题。

userEditing:表示正在编辑的用户和图片,key表示正在被编辑的图片,value表示正在编辑的用户

pictureEditRome:编辑室内的用户,key表示某个图片的编辑室,value表示这个编辑室内的用户的session。(对用户发消息是基于session来发的)

@Component
@Slf4j
public class exampleHandle extends TextWebSocketHandler {

    @Resource
    private UserService userService;
    
    //正在编辑图片的用户,key:pictureId、value:userId
    private static final ConcurrentHashMap<Long,Long> userEditing = new ConcurrentHashMap<>();

    //各个图片的编辑室 key:pictureId、value:当前图片编辑室的用户session
    private static final ConcurrentHashMap<Long, Set<WebSocketSession>> pictureEditRome = new ConcurrentHashMap<>();


    /**
     * 连接建立时,加入编辑室,并广播给所有用户
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //具体业务。。。
        /**
         * 给用户放到对应的编辑室,
         * 用户和图片的数据并没有现成的,但可以从上面的拦截器中携带过来
         * 拦截器通过attributes设置的属性可以通过形参的session获取
         * 如果拦截器设置了:
         * attributes.put("user", loginUser);
         * attributes.put("picture", picture);
         * 这里就可以通过这样获取:
         * User user = (User) session.getAttributes().get("user");
         * Picture picture = (Picture) session.getAttributes().get("picture");
         * 下面的两个方法同理,不再过多赘述
         */
        super.afterConnectionEstablished(session);
    }


    /**
     * 客户端给服务端发消息时
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

        //具体业务。。。

        super.handleTextMessage(session, message);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
    }

 具体的实现代码就不占用大家的阅读时间了,这里只提供一下广播消息(给用户发消息)是如何进行的。

因为给用户发消息的场景比较常用,可以单独抽取出来一个方法:

    /**
     * 广播消息
     * @param excludeSession 被排除的session,表示不需要广播的用户
     * @param responseMessage   这是一个自定义的实体,用于封装返回消息,可根据业务的不同自定义
     * @param picture   可以取出pictureId,表示广播给哪一张图片的编辑室用户
     * @throws IOException
     */
    public void broadcastPictureToAll(WebSocketSession excludeSession, PictureEditResponseMessage responseMessage, Picture picture) throws IOException {
        //构造广播消息
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(module); //自定义objectMapper转换规则,解决long类型在前端精度丢失问题
        String respMessageJson = objectMapper.writeValueAsString(responseMessage);
        TextMessage textMessage = new TextMessage(respMessageJson);
        //在该图片编辑室广播消息
        Set<WebSocketSession> sessions = pictureEditRome.get(picture.getId());
        for (WebSocketSession session : sessions) {
            //如果是要排除的session,那就不用对这个session广播消息
            if (excludeSession != null && excludeSession.equals(session)) {
                continue;
            }
            //如果这个session是开启的才发送
            if (session.isOpen()) {
                session.sendMessage(textMessage);
            }

        }

    }

 重点讲解:


4、注册 WebSocket 处理器

我们怎么让请求发过来时,执行的是哪一个webSocket处理类呢?以及执行哪一个拦截器呢?

这就要将他们注册了,规定一些路径、排除一些路径。与webMVC注册拦截器相似。

@Component
@EnableWebSocket //启用webSocket的重要注解
public class PictureWebSocketRoute implements WebSocketConfigurer {

    @Resource
    private PictureEditHandle pictureEditHandle;

    @Resource
    private WsHandshakeInterceptor wsHandshakeInterceptor;


    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(pictureEditHandle,"/ws/picture/edit")//注册处理类,已经选择开发的路径
                .addInterceptors(wsHandshakeInterceptor)//注册拦截器
                .setAllowedOrigins("*");//允许跨域
    }
}



使用低层 API的总结

完成上述的操作后就大功告成了,讲讲使用这种方法的特点:

特性类要继承 TextWebSocketHandler
协议层级直接处理 WebSocket 原始帧(文本/二进制)
消息格式需自行解析消息内容(如 JSON、XML)
连接管理需手动管理连接状态(如在线用户列表、心跳检测)
消息路由需自行实现消息路由逻辑(如根据消息类型分发)
广播与点对点需手动实现(如遍历所有 Session 发送消息)
鉴权与拦截需手动实现(如通过 HandshakeInterceptor 拦截连接)
集群支持需自行处理多节点消息同步(如通过 Redis 广播)

适用场景:

  • 需要完全自定义 WebSocket 协议(如私有二进制协议)。
  • 对性能有极致要求(避免 STOMP 协议解析开销)。
  • 项目规模小,消息逻辑简单,无需复杂路由。

相关文章:

  • Python基础语法1
  • C# 混淆代码工具--ConfuserEx功能与使用指南
  • 边缘计算:从概念到落地的技术解读
  • SQL语言基础(二)--以postersql为例
  • MySQL 的lock_wait_timeout 参数
  • 【C++初学】课后作业汇总复习(六) 函数模板
  • HarmonyOS: ArkUI V2装饰器-@Event:规范组件输出
  • AF3 ProteinDataset类的_patch方法解读
  • 如何在 Windows 安卓子系统 (WSA) 上安装小红书应用
  • Linux学习笔记_002:用户的基本操作
  • Node.js中URL模块详解
  • 【docker】--部署--安装docker教程
  • Linux内存管理架构(2)
  • WheatA小麦芽:农业气象大数据下载器
  • Python依赖注入完全指南:高效解耦、技术深析与实践落地
  • Midjourney 图生图:实现人物一致性的多元场景选择
  • 使用Java截取MP4文件图片的技术指南
  • Java连接MySQL数据库失败的8个关键排查点及解决方案
  • vue实现二维码生成器和解码器
  • Linux学习笔记_001:如何远程登陆?
  • 李在明回应韩国大法院判决:与自己所想截然不同,将顺从民意
  • 体坛联播|欧冠巴萨3比3战平国米,柯洁未进入国家集训队
  • 马上评|扩大高速免费救援范围,打消出行后顾之忧
  • 八成盈利,2024年沪市主板公司实现净利润4.35万亿元
  • 国务院食安办:加强五一假期食品生产、销售、餐饮服务环节监管
  • 北方旱情持续,水利部:大中型灌区春灌总体有保障