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;@Autowiredprivate 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);/*** 连接建立成功调用的方法*/@OnOpenpublic 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());}/*** 连接关闭时处理*/@OnClosepublic 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());}/*** 抛出异常时处理*/@OnErrorpublic void onError(Session session, Throwable exception) throws Exception {LOGGER.error("用户错误:,原因:" + exception.getMessage());}/*** 服务器接收到客户端消息时调用的方法*/@OnMessagepublic 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。*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}/*** 建立握手时,连接前的操作*/@Overridepublic 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所标注的对象*/@Overridepublic <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 {@Overridepublic 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.hqwc.cn/news/295410.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023 Zoho Projects项目管理软件应用案例集锦:成功案例与实践分享

Zoho Projects是Zoho旗下一款全行业、标准化、轻量级的项目管理工具。目前全球有超过20万家企业通过Zoho Projects项目管理软件开展项目工作&#xff0c;比如制定项目计划、规划流程、跟踪项目进度、协调团队等等。 本期&#xff0c;小Z带您回顾2023 Zoholics用户大会&#xff…

嵌入式系统复习--Thumb指令集

文章目录 上一篇Thumb指令集概述Thumb指令详细介绍数据处理指令数据存储指令转移指令异常中断指令 下一篇 上一篇 嵌入式系统复习–ARM指令集(二) Thumb指令集概述 在编写Thumb指令时&#xff0c;先要用伪指令CODE16声明&#xff08;ADS的编译环境下&#xff09;&#xff0c…

科研学习|论文解读——面向电商内容安全风险管控的协同过滤推荐算法研究

【论文完整内容详见知网链接】&#xff1a; 面向电商内容安全风险管控的协同过滤推荐算法研究 - 中国知网 (cnki.net) 面向电商内容安全风险管控的协同过滤推荐算法研究* 摘 要&#xff1a;[目的/意义]随着电商平台商家入驻要求降低以及商品上线审核流程简化&#xff0c;内容安…

Flutter笔记:Web支持原理与实践

Flutter笔记 Web支持原理与实践 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com CSDN&#xff1a;https://blog.csdn.net/qq_28550263/article/details/135037756 华为开发者社区…

TrustZone之可信操作系统

有许多可信内核&#xff0c;包括商业和开源的。一个例子是OP-TEE&#xff0c;最初由ST-Ericsson开发&#xff0c;但现在是由Linaro托管的开源项目。OP-TEE提供了一个功能齐全的可信执行环境&#xff0c;您可以在OP-TEE项目网站上找到详细的描述。 OP-TEE的结构如下图所示&…

软件体系结构复习

数据持久化 ORM基本概念 对象关系映射&#xff08;Object Relational Mapping&#xff0c;简称ORM&#xff09;模式是为了解决面向对象和关系数据库存在的互不匹配的现象的技术。 换言之&#xff0c;ORM是通过使用描述对象和数据库之间映射的元数据&#xff0c;把程序中的对象…

使用python对windows/win11进行属性设置

有一个个人的需求&#xff0c;针对windows系统进行属性设置&#xff0c;这里以对鼠标的左右键主键进行切换为例&#xff0c;进行了研究&#xff0c;以当前win11系统为基础进行了更动。 首先是对于如果打开windows系统下的鼠标设置&#xff0c;有以下几种办法&#xff1a; 添加…

PySpark中DataFrame的join操作

内容导航 类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统…

使用for语句换行打印久久乘法表格

一、基本思想 思想来源于九九乘法表&#xff0c;使用*来表示乘法并进行乘法的运算&#xff0c;最大的思想就是双层for循环&#xff0c;最关键的思想是进行里面的第二次for循环时&#xff0c;第二次索引的变量要小于等于第一次索引的变量。 二、 相关代码 public class Jiujiu…

c# OpenCvSharp 检测(斑点检测、边缘检测、轮廓检测)(五)

在C#中使用OpenCV进行图像处理时&#xff0c;可以使用不同的算法和函数来实现斑点检测、边缘检测和轮廓检测。 斑点检测边缘检测轮廓检测 一、斑点检测&#xff08;Blob&#xff09; 斑点检测是指在图像中找到明亮或暗的小区域&#xff08;通常表示为斑点&#xff09;&#…

聊聊kube-scheduler如何完成调度和调整调度权重

一、概述 Kube-scheduler作为k8s集群的默认调度器&#xff0c;它监听&#xff08;watch机制&#xff09;kube-apiserver&#xff0c;查询还未调度的pod&#xff0c;根据调度策略将pod调度至集群内最适合的Node 二、调度流程 首先我们通过API或者kubectl工具创建pod&#xff0c;…

Spring IoCDI

文章目录 前言什么是Spring1. 什么是 IoC 容器1.1 什么是容器1.2 什么是 IoC 2. 什么是DI IoC & DI 的使用IoC详解Bean的存储Controller注解如何获取Bean1. 根据Bean的名称获取Bean2. 根据Bean类型获取Bean3. 根据Bean名和Bean类型获取Bean Service注解Repository注解Compo…