springboot集成websocket全全全!!!

news2025/1/22 12:58:36

一、界面展示

二、前置了解

1.什么是websocket

WebSocket是一种在单个TCP连接上进行全双工通信的持久化协议。
全双工协议就是客户端可以给我们服务器发数据 服务器也可以主动给客户端发数据。

2.为什么有了http协议 还要websocket 协议

http协议是一种无状态,非持久化的单全双工应用层协议。
主要用于一问一答的方式交付信息,即客户端发送请求,服务器返回响应。这种模式适合于获取数据或者提交数据的场景。

所以http协议中,服务器无法主动给客户端发送数据,导致出现服务器数据状态发生改变,客户端无法感知。

针对上面的问题,http 勉强可以通过 定时轮询 和 长轮询 解决问题。

定时轮询:客户端不断地定时请求服务器, 询问数据状态变更的情况。
定时轮询的弊端:存在延时,浪费服务器资源和带宽,存在大量无效请求。

长轮询:拉长请求时间,客户端发送请求后,服务器在没有新数据时不会立即响应,而是等到有新数据时才返回响应。这种方法可以减少无效的请求,
长轮询的弊端:仍然需要频繁地建立和断开连接,且服务器需要维护未完成的请求,这可能会占用大量的服务器资源。

承上启下 所有最后我们websocket应运而生,它就是为了解决这个问题而设计的。
WebSocket协议可以实现全双工通信,即客户端和服务器可以在任何时候 相互 主动发送数据。此外,一旦WebSocket连接建立,客户端和服务器之间的连接将保持活动状态,直到被任何一方关闭。

三、附集成代码

1.引入pom依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
2.使用@ServerEndpoint创建WebSocket Endpoint
package com.ruoyi.framework.websocket;

import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.WebSocketConfig;
import com.ruoyi.framework.web.service.TokenService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.LoggerFactory;

/**
 * @author qujingye
 * @Classname WebSocketServer
 * @Description  虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean
 * @Date 2023/12/19 16:11
 */
@Component
@ServerEndpoint(value = "/websocket/message", configurator = WebSocketConfig.class)
public class WebSocketServer {

    private static TokenService tokenService;

    @Autowired
    private void setOriginMessageSender(TokenService tokenService) {
        WebSocketServer.tokenService = tokenService;
    }

    /**
     * WebSocketServer 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);

    private final static ConcurrentHashMap<Long, CopyOnWriteArrayList<Session>> sessionPool = new ConcurrentHashMap<>();

    private final static AtomicLong atomicLong = new AtomicLong(0L);


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) throws Exception {
        Long userId = parseUserId(session);
        System.out.println(userId);
        LOGGER.info("[WebSocket] 有新的连接, 当前用户id: {}", userId);
        if (userId == null) {
            return;
        }
        CopyOnWriteArrayList<Session> sessions = sessionPool.get(userId);
        //不存在其他人登陆
        if (null == sessions) {
            sessions = new CopyOnWriteArrayList<>();
        }
        sessions.add(session);
        sessionPool.put(userId, sessions);
        atomicLong.getAndIncrement();
        LOGGER.info("[WebSocket] 有新的连接, 当前连接数: {}", atomicLong.get());

    }

    /**
     * 连接关闭时处理
     */
    @OnClose
    public void onClose(Session session) {
        Long userId = parseUserId(session);
        if (userId == null) {
            return;
        }
        CopyOnWriteArrayList<Session> sessions = sessionPool.remove(userId);
        CopyOnWriteArrayList<Session> newSessions = new CopyOnWriteArrayList<>();
        for (Session s : sessions) {
            if (!s.getId().equals(session.getId())) {
                newSessions.add(s);
            }
        }
        sessionPool.put(userId, newSessions);
        atomicLong.getAndDecrement();
        LOGGER.info("[WebSocket] 连接断开, 当前连接数: {}", atomicLong.get());
    }

    /**
     * 抛出异常时处理
     */
    @OnError
    public void onError(Session session, Throwable exception) throws Exception {
        LOGGER.error("用户错误:,原因:" + exception.getMessage());
    }

    /**
     * 服务器接收到客户端消息时调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        //把收到的消息发回去
        session.getAsyncRemote().sendText(message);
        LOGGER.info("message: {}", message);
    }


    /**
     * 给该用户id的全部发送消息
     */
    public void sendMessage(Long userId, String message) {
        CopyOnWriteArrayList<Session> sessions = sessionPool.get(userId);
        if (null == sessions || sessions.size() == 0) {
            return;
        }
        sessions.forEach(s -> s.getAsyncRemote().sendText(message));
    }

    /**
     * 获取用户id
     */
    private Long parseUserId(Session session) {
        String token = (String) session.getUserProperties().get(WebSocketConfig.WEBSOCKET_PROTOCOL);
        if (StringUtils.isNotEmpty(token)) {
            LoginUser loginUser = tokenService.getLoginUserByToken(token);
            if (loginUser != null) {
                return loginUser.getUserId();
            }
        }
        return null;
    }
}
3.定义WebSocketConfig

注入ServerEndpointExporter来自动注册端点

package com.ruoyi.framework.config;

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

import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.List;
import java.util.Map;

/**
 * @author qujingye
 * @Classname WebSocketConfig
 * @Description 继承服务器断点配置类
 * @Date 2023/12/19 16:08
 */
@Configuration
public class WebSocketConfig extends ServerEndpointConfig.Configurator {

    /**
     * WebSocket的协议头
     */
    public final static String WEBSOCKET_PROTOCOL = "Sec-Websocket-Protocol";

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


    /**
     * 建立握手时,连接前的操作
     */
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        // 这个用户属性userProperties 可以通过 session.getUserProperties()获取
        final Map<String, Object> userProperties = sec.getUserProperties();
        Map<String, List<String>> headers = request.getHeaders();
        List<String> protocol = headers.get(WEBSOCKET_PROTOCOL);
        // 存放自己想要的header信息
        if (protocol != null) {
            userProperties.put(WEBSOCKET_PROTOCOL, protocol.get(0));
        }


    }

    /**
     * 创建端点实例,也就是被@ServerEndpoint所标注的对象
     */
    @Override
    public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
        return super.getEndpointInstance(clazz);
    }

}
4.定义过滤器设置响应头
package com.ruoyi.framework.security.filter;

import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.WebSocketConfig;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * FileName:     com.admin.security.filter WebsocketFilter
 * Date:         2023/8/1 16:42
 *
 * @author Messylee
 */
@Order(1)
@Component
@WebFilter(filterName = "WebsocketFilter", urlPatterns = "/ws/**")
public class WebsocketFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest headers = (HttpServletRequest) servletRequest;
        String token = headers.getHeader(WebSocketConfig.WEBSOCKET_PROTOCOL);
        if (StringUtils.isNotEmpty(token)){
            response.setHeader(WebSocketConfig.WEBSOCKET_PROTOCOL, token);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

}
5.vue前端界面代码

<template>
  <div class="app-container home">
    <el-row :gutter="20">
      <el-col :sm="24" :lg="24">
        <h1>集成websocket测试</h1>
      </el-col>
    </el-row>
    <el-row :gutter="20">
      <el-col :sm="24" :lg="24">
        <div>
          <el-input v-model="url" type="text" style="width: 20%" /> &nbsp;
          &nbsp;
          <el-button @click="join" type="primary">连接</el-button>
          <el-button @click="exit" type="danger">断开</el-button>
          <el-button @click="resetForm" type="success">重置</el-button>
          <br />
          <br />
          <el-input type="textarea" v-model="message" :rows="9" />
          <br />
          <br />
          <el-button type="success" @click="send">发送消息</el-button>
          <br />
          <br />
          返回内容
          <el-input type="textarea" v-model="text_content" :rows="9" />
          <br />
          <br />
        </div>
      </el-col>
    </el-row>
  </div>
</template>


<script>
import { getToken } from "@/utils/auth";

export default {
  name: "Index",
  data() {
    return {
      url: "ws://127.0.0.1:8080/websocket/message",
      message: "",
      text_content: "",
      ws: null,
      headers: {
        Authorization: "Bearer " + getToken(),
      },
    };
  },
  methods: {
    join() {
      const wsuri = this.url;
      // this.ws = new WebSocket(wsuri);
      this.ws = new WebSocket(wsuri, [getToken()]);
      const self = this;
      // 连接成功后调用
      this.ws.onopen = function (event) {
        self.text_content = self.text_content + "WebSocket连接成功!" + "\n";
      };
      this.ws.onerror = function (event) {
        self.text_content = self.text_content + "WebSocket连接发生错误!" + "\n";
      };
      // 接收后端消息
      this.ws.onmessage = function (event) {
        self.text_content = self.text_content + event.data + "\n";
      };
      // 关闭连接时调用
      this.ws.onclose = function (event) {
        self.text_content = self.text_content + "已经关闭连接!" + "\n";
      };
    },
    exit() {
      if (this.ws) {
        this.ws.close();
        this.ws = null;
      }
    },
    send() {
      if (this.ws) {
        this.ws.send(this.message);
      } else {
        alert("未连接到服务器");
      }
    },
    //重置
    resetForm() {
      this.message = "";
      this.text_content = "";
    },
  },
};
</script>

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

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

相关文章

基于SpringBoot的APK检测管理系统 JAVA简易版

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 开放平台模块2.3 软件档案模块2.4 软件检测模块2.5 软件举报模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 开放平台表3.2.2 软件档案表3.2.3 软件检测表3.2.4 软件举报表 四、系统展示五、核心代…

学校和老师如何制作自己免费的成绩查询系统

在当今数字化的时代&#xff0c;许多学校都采用信息技术来管理和提高工作效率。其中&#xff0c;成绩查询系统是一个非常实用的工具&#xff0c;它可以让老师和学生们快速、方便地查询成绩。那么&#xff0c;学校和老师如何制作自己免费的成绩查询系统呢&#xff1f;本文将为你…

【Amazon 实验②】使用Amazon WAF做基础 Web Service 防护之自定义规则

文章目录 1. 自定义规则1.1 介绍 2. 实验步骤2.1 测试2.2 输出 上一篇章介绍了使用Amazon WAF做基础 Web Service 防护中的Web ACLs 配置 & AWS 托管规则的介绍和演示操作 【Amazon 实验①】使用Amazon WAF做基础 Web Service 防护&#xff0c;本篇章将继续介绍关于自定义…

webSocket原理及其案例

常见的消息推送方式 1&#xff1a;轮询方式 浏览器以指定的时间间隔向服务器发出HTTP请求&#xff0c;服务器实现试试返回数据给浏览器 缺点&#xff1a;数据有延时、服务器压力较大。 2&#xff1a;长轮询 浏览器发出ajax&#xff08;异步&#xff09;请求&#xff0c;服…

【HarmonyOS开发】ArkTs使用Http封装

1、鸿蒙中如何进行网络请求 1.1 三方库请求 ohos/axios ohos/retrofit ohos/httpclient 1.2 鸿蒙原生请求 ohos.net.http 2、ArkTs请求模块ohos.net.http 本模块提供HTTP数据请求能力。应用可以通过HTTP发起一个数据请求&#xff0c;支持常见的GET、POST、OPTIONS、HEAD…

阿里云吴结生:云计算是企业实现数智化的阶梯

云布道师 近年来&#xff0c;越来越多人意识到&#xff0c;我们正处在一个数据爆炸式增长的时代。IDC 预测 2027 年全球产生的数据量将达到 291 ZB&#xff0c;与 2022 年相比&#xff0c;增长了近 2 倍。其中 75% 的数据来自企业&#xff0c;每一个现代化的企业都是一家数据公…

Xcode15 iOS 17 Simulator 离线安装,模拟器安装

Xcode 15 安装包的大小相比之前更小&#xff0c;因为除了 macOS 的 Components&#xff0c;其他都需要动态下载安装&#xff0c;否则提示 iOS 17 Simulator Not Installed。 如果不安装对应的运行模拟库 无法真机和模拟器运行&#xff0c;更无法新建项目。但是由于模拟器安装包…

通过for语句遍历一个简单的数组

一、基本思想 创建一个命名为ArrayDemo的类&#xff0c;然后定义一个合适的数组&#xff0c;使用for语句遍历这个数值&#xff0c;然后进行输出。 注意事项&#xff1a; 最好在每个字符之间留下一个空白。 二、基本代码 public class ArrayDemo {public static void main(St…

【沐风老师】3dMax篮球建模方法详解

3dMax足球、排球和篮球建模系列之&#xff1a;篮球建模。对于足球和排球建模&#xff0c;思路是从一个基础模型开始&#xff0c;利用这个基础模型与最终的足球&#xff08;或排球&#xff09;模型的某些相似之处&#xff0c;经过修改编辑&#xff0c;最终完成目标模型的建模。但…

【Amazon 实验②】Amazon WAF功能增强之使用Cloudfront、Lambda@Edge阻挡攻击

文章目录 一、方案介绍二、架构图三、部署方案1. 进入Cloud9 编辑器&#xff0c;新打开一个teminal2. 克隆代码3. 解绑上一个实验中Cloudfront 分配绑定的防火墙4. 使用CDK部署方案5. CDK部署完成6. 关联LambdaEdge函数 四、方案效果 一、方案介绍 采用 LambdaEdge DynamoDB 架…

【Unity】入门

文章目录 概述常用组件各类文件基础知识创建工程工程目录介绍五个窗口面板创建代码和场景 脚本与编程鼠标的输入键盘的输入代码来操作组件获取物体API资源的使用API定时调用与线程向量的基本运算预制体与实例 物理系统与组件案例实操作快捷键来源 Unity已广泛运用到各个领域&am…

使用PE信息查看工具和Dependency Walker工具排查因为库版本不对导致程序启动报错问题

目录 1、问题说明 2、问题分析思路 3、问题分析过程 3.1、使用Dependency Walker打开软件主程序&#xff0c;查看库与库的依赖关系&#xff0c;查看出问题的库 3.2、使用PE工具查看dll库的时间戳 3.3、解决办法 4、最后 VC常用功能开发汇总&#xff08;专栏文章列表&…

C++ map和vector向量使用方法

C map用法 C 中 map 提供的是一种键值对容器&#xff0c;里面的数据都是成对出现的,如下图&#xff1a;每一对中的第一个值称之为关键字(key)&#xff0c;每个关键字只能在 map 中出现一次&#xff1b;第二个称之为该关键字的对应值。 map的使用 需要导入头文件 #include …

金蝶云星空业务对象添加网络互控存储在哪些表

文章目录 金蝶云星空业务对象添加网络互控存储在哪些表【网控操作列表】确定后数据写入《网络控制对象》主表《网络控制对象》多语言 二、【网络互斥列表】数据写入《网络控制互斥对象》 金蝶云星空业务对象添加网络互控存储在哪些表 【网控操作列表】确定后数据写入 《网络控…

ssm基于web的汽车售后服务管理系统的设计与实现论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对汽车售后服务信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

【数字通信原理】复习笔记

哈喽&#xff89;hi~ 小伙伴们许久没有更新啦~ 花花经历了漫长的考试周~ 要被累成花干啦。今天来更新《数字通信原理》手写笔记给需要的小伙伴~ &#xff08;注:这是两套笔记&#xff0c;是需要结合来看的哦~&#xff09; 第一套的笔记请结合bilibili:张锦皓的复习课程来哦。 第…

点击筛选框动态增加 多条可输入Table列 以及通过操作数组改造数据

点击筛选框动态增加 多条可输入Table列 以及通过操作数组改造数据 <el-col :span"8" class"tab_group"><el-form-item label"动态筛选"><el-select v-model.trim"ruleForm.flowType" placeholder"请选择" …

【DOM笔记三】节点操作(节点概述、节点层级、添加 / 删除 / 复制节点、DOM基本语法总结!

文章目录 5 节点操作5.1 节点概述5.2 节点层级5.2.1 父子节点5.2.2 兄弟节点 5.3 添加元素5.4 删除节点5.5 复制节点5.6 三种动态创建元素的区别 6 DOM小结 5 节点操作 获取元素的方式&#xff1a; 比较发现&#xff0c;用节点层级关系来获取元素更简单&#xff08;DOM方法相当…