springboot集成websocket全全全!!!

一、界面展示

二、前置了解

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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

红帆iOffice iorepsavexml.aspx接口存在任意文件上传漏洞 附POC

@[toc] 红帆iOffice iorepsavexml.aspx接口存在任意文件上传漏洞 附POC 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅…

为什么react call api in cDidMount

为什么react call api in cDM 首先&#xff0c;放到constructor或者cWillMount不是语法错误 参考1 参考2 根据上2个参考&#xff0c;总结为&#xff1a; 1、官网就是这么建议的&#xff1a; 2、17版本后的react 由于fiber的出现导致 cWM 会调用多次&#xff01; cWM 方法已…

16-高并发-队列术

队列&#xff0c;在数据结构中是一种线性表&#xff0c;从一端插入数据&#xff0c;然后从另一端删除数据。 在我们的系统中&#xff0c;不是所有的处理都必须实时处理&#xff0c;不是所有的请求都必须实时反馈结果给用户&#xff0c;不是所有的请求都必须100%一次性处理成功…

如何查看内存卡使用记录-查看的设备有:U盘、移动硬盘、MP3、SD卡等-供大家学习研究参考

主要功能 USB Viewer&#xff08;USB移动存储设备使用记录查看器&#xff09;可用于查看本机的USB移动存储设备使用记录。可查看的设备有&#xff1a;U盘、移动硬盘、MP3、SD卡……等。   可用于兵器、航空、航天、政府、军队等对保密要求较高的单位&#xff0c;可在计算机保…

ubuntu22.04+ROS2推荐匹配的gazebo版本

放大以后看到&#xff1a; 可以看到ros2推荐使用版本是humble-----匹配的是Ubuntu22.04LTS -------匹配gazebo Harmonic

论文阅读——Flamingo

Flamingo: a Visual Language Model for Few-Shot Learning 模型建模了给定交织的图片或支视频的条件下文本y的最大似然&#xff1a; 1 Visual processing and the Perceiver Resampler Vision Encoder&#xff1a;from pixels to features。 预训练并且冻结的NFNet&#xff…

每次maven刷新jdk都要重新设置

pom.xml <java.version>17</java.version> 改为<java.version>1.8</java.version>

【LeetCode:1962. 移除石子使总数最小 | 堆 + 贪心】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

变限积分求导(带参,极限)

方法 一般形 带参数方程形 带极限型

Koordinator 支持 K8s 与 YARN 混部,小红书在离线混部实践分享

作者&#xff1a;索增增&#xff08;小红书&#xff09;、宋泽辉&#xff08;小红书&#xff09;、张佐玮&#xff08;阿里云&#xff09; 背景介绍 Koordinator 是一个开源项目&#xff0c;基于阿里巴巴在容器调度领域多年累积的经验孵化诞生&#xff0c;目前已经支持了 K8s…

Windows安装cnpm报错 The operation was rejected by your operating system.

Windows在安装cnpm时出现如下错误 npm ERR! The operation was rejected by your operating system. npm ERR! Its possible that the file was already in use (by a text editor or antivirus), npm ERR! or that you lack permissions to access it. npm ERR! npm ERR! If y…

Vue3中使用props和emits详解

前言 在Vue3中&#xff0c;父子组件之间的数据传递是一个常见的需求。本文将介绍如何在Vue3中传递对象&#xff0c;并且在子组件中访问和修改父组件对象中的属性值&#xff0c;以及子组件如何调用父组件中的方法。 在 Vue 3 中&#xff0c;父子组件之间传值有以下作用&#xf…

Chatgpt如何共享可以防止封号!

ChatGPT 是一个基于 GPT-3.5/GPT-4 模型的对话系统&#xff0c;它主要用于处理自然语言对话。通过训练模型来模拟人类的语言行为&#xff0c;ChatGPT 可以通过文本交流与用户互动。每个新版本的 GPT 通常都会在模型规模、性能和其他方面有一些改进。在目前免费版GPT-3.5 中&…

【Vulnhub 靶场】【Corrosion: 1】【简单】【20210731】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/corrosion-1,730/ 靶场下载&#xff1a;https://download.vulnhub.com/corrosion/Corrosion.ova 靶场难度&#xff1a;简单 发布日期&#xff1a;2021年07月31日 文件大小&#xff1a;7.8 GB 靶场作者&#xf…

mysql自增序列 关于mysql线程安全 独享内存 溢出 分析

1 MySQL锁概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0c;锁冲突也是影响数据库并发访问性能的一个重要因素。 相对其他数据库而言&#xff0c;MySQL的锁机制比较简单&#xff0c…

关于Triple DES(3DES)对称加密算法

一、引言 在网络安全领域&#xff0c;对称加密算法作为一种常见的加密手段&#xff0c;被广泛应用于保障数据传输的保密性和完整性。其中&#xff0c;DES&#xff08;Data Encryption Standard&#xff09;算法作为一种经典的对称加密算法&#xff0c;由IBM于1970年代开发&…

「数据结构」二叉树2

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;初阶数据结构 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 文章目录 &#x1f349;前言&#x1f349;链式结构&#x1f349;遍历二叉树&#x1f34c;前序遍历&#x1f34c;中序遍历&#x…

PromptNER: Prompt Locating and Typing for Named Entity Recognition

原文链接&#xff1a; https://aclanthology.org/2023.acl-long.698.pdf ACL 2023 介绍 问题 目前将prompt方法应用在ner中主要有两种方法&#xff1a;对枚举的span类型进行预测&#xff0c;或者通过构建特殊的prompt来对实体进行定位。但作者认为这些方法存在以下问题&#xf…

Python入门学习篇(五)——列表字典

1 列表 1.1 定义 ①有序可重复的元素集合 ②可以存放不同类型的数据 ③个人理解:类似于java中的数组1.2 相关方法 1.2.1 获取列表长度 a 语法 len(列表名)b 示例代码 list2 [1, 2, "hello", 4] print(len(list2))c 运行结果 1.2.2 获取列表值 a 语法 列表名…

渗透实验 XSS和SQL注入(Lab3.0)

windows server2003IIS搭建 配置2003的虚拟机 1、利用AWVS扫描留言簿网站&#xff08;安装见参考文档0.AWVS安装与使用.docx&#xff09;&#xff0c;发现其存在XSS漏洞&#xff0c;截图。 2、 Kali使用beef生成恶意代码 cd /usr/share/beef-xss./beef执行上面两条命令 …