springboot集成websocket

1:什么是 WebSocket?

WebSocket 是一种网络通信协议,它在单个 TCP 连接上提供全双工 (Full-Duplex)的通信信道。这意味着客户端(如浏览器)和服务器可以同时、独立地发送和接收数据,实现了真正的实时、双向对话。

2:为什么需要 WebSocket?

在 WebSocket 出现之前,实现实时数据更新(如聊天、实时游戏、股票行情)主要靠以下"笨办法":

轮询(Polling):客户端每隔几秒就向服务器发送一个 HTTP 请求问:"有新数据吗?"。即使没有数据,也会频繁请求,造成巨大的网络和服务器资源浪费。

长轮询(Long-Polling):客户端发送一个请求,服务器hold住这个连接,直到有数据更新或超时才返回响应。客户端收到响应后立即再发起一个新的请求。这种方式比普通轮询好,但每次请求仍然包含冗长的 HTTP 头信息,效率不高。

WebSocket 的优势:

低延迟:连接建立后,数据可以瞬间到达,没有 HTTP 请求的延迟。

高效:一旦连接建立,后续通信的数据包包头很小(通常只有 2-10 字节),远小于 HTTP 每次请求都携带的冗长头部。

全双工通信:服务器可以在任何需要的时候主动"推送"数据给客户端,而不需要客户端先请求。

3:应用场景

WebSocket 非常适合需要高实时性的应用:

即时通讯(Chat):微信网页版、Slack、Discord。

多人在线游戏:实时同步玩家位置和状态。

实时数据推送:股票行情、体育赛事实时比分、实时监控系统。

协同工具:多人同时在线编辑文档(如 Google Docs)。

物联网(IoT):设备状态的实时控制和监控。

4:实现

1)加载依赖

XML 复制代码
<!-- mybatis-plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>

2)配置WebSocket

java 复制代码
package ***.***;

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

@Configuration
public class WEebSocketConfig {

    /**
     * ServerEndpointExporter 会自动注册使用ServerEndpoint注解的websocket endpoint
     *
     * @param
     * @return org.springframework.web.socket.server.standard.ServerEndpointExporter
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

3)创建抽象基类

java 复制代码
package ***.***;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.Session;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public abstract class BaseWebsocket {

    static Logger logger = LoggerFactory.getLogger(BaseWebsocket.class);

    protected static void _sendMessage(String msg, Set<Session> set) {
        try {
            Iterator<Session> it = set.iterator();
            while (it.hasNext()) {
                Session s = it.next();
                if (s.isOpen()) {
                    synchronized (s) {
                        s.getBasicRemote().sendText(msg);
                    }
                } else {
                    it.remove();
                }
            }
        } catch (Throwable t) {
            logger.error(t.getMessage(), t);
        }
    }

    // 发送消息
    public static void sendMessage(String msg, Set<Session> set) {
        _sendMessage(msg, set);
    }

    // 连接建立时触发
    public abstract void onOpen(Session session);

    // 连接关闭时触发
    public abstract void onCLose(String num, Session session);

    // 接收数据时触发
    public abstract void onMessage(String msg, Session session);

    // 通信发生错误时触发
    public abstract void onError(Throwable t);

    // 获取所用连接的session信息,不区分key时用此方法
    public abstract Set<Session> getSet();

    // 获取所有连接的key,session信息,需要根据key区分时使用此方法
    public abstract Map<String, Set<Session>> getMap();

    // 指定key连接
    protected void _onOpen(String num, Session session) {
        Map<String, Set<Session>> map = getMap();
        Set<Session> set = map.get(num);
        if (null == set) {
            set = new HashSet<>();
            map.put(num, set);

        }
        set.add(session);
    }

    // 不指定key连接
    protected void _onOpen(Session session) {
        getSet().add(session);
    }

    // 指定key关闭
    protected void _onClose(String num, Session session) {
        Map<String, Set<Session>> map = getMap();
        Set<Session> set = map.get(num);
        if (set != null) {
            set.remove(session);
        }
    }
    
    // 不指定key关闭
    protected void _onClose(Session session) {
        getSet().remove(session);
    }

    // 抛出错误信息
    protected void _onError(Throwable t) {
        logger.error(t.getMessage());
    }

}

4)创建websocket实例

java 复制代码
package com.ruoyi.lxsjgl.ws;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.lxsjgl.domain.JzfzxnxtLxsjglRwsxjl;
import com.ruoyi.lxsjgl.domain.JzfzxnxtLxsjglSbxx;
import com.ruoyi.lxsjgl.domain.LxsjglConstants;
import com.ruoyi.lxsjgl.service.IJzfzxnxtLxsjglSbxxService;
import com.ruoyi.lxsjgl.service.impl.JzfzxnxtLxsjglRwsxjlServiceImpl;
import com.ruoyi.lxsjgl.service.impl.JzfzxnxtLxsjglSbxxServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 测试 WS
 * 我写了两种方法,指定连接目标/针对所有连接
 * 如无需指定目标通信,将 @OnOpen @OnClose @OnMessage @OnError 与注释的交换即可
 */
@Component
@ServerEndpoint("/websocket/testws/{key}")
// @ServerEndpoint("/websocket/testws")
public class TestWS extends BaseWebsocket {

    private static JzfzxnxtLxsjglRwsxjlServiceImpl rwmrczjlService;
    private static IJzfzxnxtLxsjglSbxxService sbxxService;

    @Resource
    public void setRwmrczjlService(JzfzxnxtLxsjglRwsxjlServiceImpl rwmrczjlService) {
        TestWS.rwmrczjlService = rwmrczjlService;
    }

    @Resource
    public void setSbxxService(JzfzxnxtLxsjglSbxxServiceImpl sbxxService) {
        TestWS.sbxxService = sbxxService;
    }

    private static final Set<Session> set = new CopyOnWriteArraySet<>();

    private static final Map<String, Set<Session>> map = new ConcurrentHashMap<>();

    public static Logger logger = LoggerFactory.getLogger(TestWS.class);

    public static void sendMessage2All(String msg) {
        _sendMessage(msg, set);
    }

    public static void sendMessage(String key, String msg) {
        Set<Session> sessions = map.get(key);
        _sendMessage(msg, sessions);
        logger.info(System.currentTimeMillis() + " --> " + key + " --> " + "/websocket/testws 发送消息:" + msg);
    }

    //    @OnOpen
    public void onOpen(Session session) {
        _onOpen(session);
        logger.info(System.currentTimeMillis() + " --> 建立websocket连接:/websocket/testws");
    }

    @OnOpen
    public void onOpen(@PathParam("key") String key, Session session) {
        _onOpen(key, session);
        logger.info(System.currentTimeMillis() + " --> " + key + " --> " + "建立websocket连接:/websocket/testws");
    }

    //    @OnClose
    public void onCLose(Session session) {
        _onClose(session);
        logger.info(System.currentTimeMillis() + " --> 关闭websocket连接:/websocket/testws");
    }

    @OnClose
    public void onCLose(@PathParam("key") String key, Session session) {
        _onClose(key, session);
        logger.info(System.currentTimeMillis() + " --> " + key + " --> " + "关闭websocket连接:/websocket/testws");
    }

    //    @OnMessage
    public void onMessage(String msg, Session session) {
        if (msg.equals("ping")) {
            sendMessage2All("pong");
        } else {
            logger.info(System.currentTimeMillis() + " --> " + "/websocket/testws收到消息:" + msg);
        }
    }

    @OnMessage
    public void onMessage(@PathParam("key") String key, String msg, Session session) {
        if (msg.equals("ping")) { // 心跳消息
            sendMessage(key, "pong");
        } else {
            // 此处可以写收到消息后的具体实现
            logger.info(System.currentTimeMillis() + " --> " + key + " --> " + "/websocket/testws 收到消息:" + msg);
        }
    }

    @OnError
    public void onError(Throwable t) {
        _onError(t);
    }

    @Override
    public Set<Session> getSet() {
        return set;
    }

    @Override
    public Map<String, Set<Session>> getMap() {
        return map;
    }
}

5)测试截图

相关推荐
paopaokaka_luck2 小时前
绿色环保活动平台(AI问答、WebSocket即时通讯、协同过滤算法、Echarts图形化分析)
java·网络·vue.js·spring boot·websocket·网络协议·架构
wow_DG2 小时前
【WebSocket✨】入门之旅(三):WebSocket 的实战应用
网络·websocket·网络协议
玉衡子2 小时前
四、索引优化实战
java·后端
程序员爱钓鱼2 小时前
Go语言实战案例 — 工具开发篇:编写一个进程监控工具
后端·google·go
老华带你飞3 小时前
畅阅读小程序|畅阅读系统|基于java的畅阅读系统小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序·毕设·畅阅读系统小程序
canonical_entropy3 小时前
不同的工作需要不同人格的AI大模型?
人工智能·后端·ai编程
IT_陈寒3 小时前
Vite 5.0 终极优化指南:7个配置技巧让你的构建速度提升200%
前端·人工智能·后端
小熊学Java3 小时前
基于 Spring Boot+Vue 的高校竞赛管理平台
vue.js·spring boot·后端
钢门狂鸭9 小时前
关于rust的crates.io
开发语言·后端·rust