前端订阅后端推送WebSocket定时任务

0.需求

        后端定时向前端看板推送数据,每10秒或者30秒推送一次。

1.前言知识

        HTTP协议是一个应用层协议,它的特点是无状态、无连接和单向的。在HTTP协议中,客户端发起请求,服务器则对请求进行响应。这种请求-响应的模式意味着服务器无法主动向客户端发送消息。

        这种单向通信的缺点在于,如果服务器有持续的状态变化,客户端要获取这些变化就很困难。为了解决这个问题,许多Web应用采用了一种叫做长轮询的技术,即频繁地通过AJAX和XML发起异步请求来检查服务器的状态。但这种方式效率较低,也很浪费资源,因为需要不断地建立连接或保持连接打开。

        而WebSocket则是一种不同的通信协议,它允许客户端和服务器之间进行全双工通信。这意味着无论是客户端还是服务器,都可以随时通过已经建立的连接向对方发送数据。而且,WebSocket只需要建立一次连接就可以保持通信状态,无需频繁地建立和断开连接,因此效率大大提高。

        总结一下,HTTP协议虽然广泛应用,但因其单向通信的局限性,在处理服务器状态持续变化的情况时显得力不从心。而WebSocket协议则通过全双工通信的方式,有效地解决了这个问题,提高了通信效率。

2.后端实现

2.1不带参数

2.1.1添加依赖:

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

2.1.2websocket配置:

/*** 通过EnableWebSocketMessageBroker* 开启使用STOMP协议来传输基于代理(message broker)的消息,* 此时浏览器支持使用@MessageMapping 就像支持@RequestMapping一样。*///WebSocket的配置类
@Configuration
//开启对WebSocket的支持
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{/*** 注册stomp的端点* 注册一个STOMP协议的节点,并映射到指定的URL*/@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {//endPoint 注册协议节点,并映射指定的URl点对点-用//注册一个名字为"/endpointSocket" 的endpoint,并指定 SockJS协议。//允许使用socketJs方式访问,访问点为webSocketServer,允许跨域//连接前缀//配置客户端尝试连接地址//广播registry.addEndpoint("/ws/public").setAllowedOriginPatterns("*").withSockJS();//点对点registry.addEndpoint("/ws/private").setAllowedOriginPatterns("*").withSockJS();}/*** 通过实现 WebSocketMessageBrokerConfigurer 接口和加上 @EnableWebSocketMessageBroker 来进行 stomp 的配置与注解扫描。* 其中覆盖 registerStompEndpoints 方法来设置暴露的 stomp 的路径,其它一些跨域、客户端之类的设置。* 覆盖 configureMessageBroker 方法来进行节点的配置。* 其中 enableSimpleBroker配置的广播节点,也就是服务端发送消息,客户端订阅就能接收消息的节点。* 覆盖setApplicationDestinationPrefixes方法,设置客户端向服务端发送消息的节点。* 覆盖 setUserDestinationPrefix 方法,设置一对一通信的节点。** @param registry*/@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理,即设置广播节点registry.enableSimpleBroker("/topic","/user");//后端接收的主题前缀,即客户端向服务端发送消息需要有/client前缀
//        registry.setApplicationDestinationPrefixes("/client");//指定用户发送(一对一)的前缀/user/
//        registry.setUserDestinationPrefix("/user/");}
}

2.1.3后端代码 

        一个是订阅请求接口,一个是关闭定时任务接口。这段代码实现了一个基于WebSocket的定时推送机制,允许通过发送WebSocket消息来启动和关闭定时任务,从而每30秒推送一次数据。

  /*** 看板接口-不带参数* 定时任务(每30秒推送一次)*/@MessageMapping("/backend/produce/summary")public void pushProduceSummary() {log.info("服务端接收到消息: {}");if (scheduledTask.get("pushProduceSummary") == null) {ScheduledFuture<?> future = executorService.scheduleAtFixedRate(() -> {ProgressVO progressVO = progressSummaryService.summary();String destination = "/topic/backend/produce/summary";template.convertAndSend(destination, progressVO);log.info("已推送信息,每30秒推送一次:{}");}, 1, 30, TimeUnit.SECONDS);scheduledTask.put("pushProduceSummary", future);} else {log.info("定时任务已开始!");}}
/*** 关闭/backend/produce/summary接口的定时任务** @author weiq*/@MessageMapping("/close/backend/produce/summary")public void cancelPushProduceSummary() {scheduledTask.forEach((StringKey, future) -> {if (future != null && !future.isCancelled() && StringKey.equals("pushProduceSummary")) {// 清除定时任务的引用scheduledTask.remove("pushProduceSummary");boolean cancel = future.cancel(true);if (cancel) {log.info("已关闭定时任务Key={}",StringKey);}else{log.info("失败关闭定时任务Key={}",StringKey);}}});}

2.2带参数

        一个是订阅请求接口,一个是关闭定时任务接口。

  • 当客户端向 /backend/produce/runEfficiency/{startTime}/{endTime} 这个 WebSocket 地址发送消息时,pushProduceRunEfficiency 方法会被调用。
  • 这个方法会检查是否已有一个定时任务在运行。如果没有,它会创建一个新的定时任务,该任务会每30秒从 runEfficiencyService 获取运行效率数据,并通过 WebSocket 发送到指定的主题(destination)。
  • 前端(或任何监听该主题的 WebSocket 客户端)需要事先订阅这个主题,以便能够接收后端发送的数据。
 /*** (看板)*定时任务(每30秒推送一次)* @param startTime* @param endTime*/@MessageMapping("/backend/produce/runEfficiency/{startTime}/{endTime}")public void pushProduceRunEfficiency(@DestinationVariable Long startTime, @DestinationVariable Long endTime) {log.info("服务端接收到消息: startTime={},endTime={}", startTime, endTime);if (scheduledTask.get("pushProduceRunEfficiency") == null) {ScheduledFuture<?> future = executorService.scheduleAtFixedRate(() -> {List<RunVO> runVOList = runEfficiencyService.run(startTime, endTime);String destination = "/topic/backend/produce/runEfficiency" + "/" + startTime + "/" + endTime;template.convertAndSend(destination, runVOList);log.info("已推送信息,每30秒推送一次:{}");}, 1, 30, TimeUnit.SECONDS);scheduledTask.put("pushProduceRunEfficiency", future);}else{log.info("定时任务已开启!");}}/*** 关闭/backend/produce/runEfficiency/{startTime}/{endTime}接口的定时任务** @author weiq*/@MessageMapping("/close/backend/produce/runEfficiency")public void cancelPushProduceRunEfficiency() {scheduledTask.forEach((StringKey, future) -> {if (future != null && !future.isCancelled() && StringKey.equals("pushProduceRunEfficiency")) {// 清除定时任务的引用scheduledTask.remove("pushProduceRunEfficiency");boolean cancel = future.cancel(true);if (cancel) {log.info("已关闭定时任务Key={}",StringKey);} else {log.info("失败定时任务Key={}",StringKey);}}});}

3.前端验证

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script><script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script><script src="https://code.jquery.com/jquery-3.2.0.min.js"integrity="sha256-JAW99MJVpJBGcbzEuXk4Az05s/XyDdBomFqNlM3ic+I=" crossorigin="anonymous"></script><script type="text/javascript">var stompClient = null;function setConnected(connected) {document.getElementById("connect").disabled = connected;document.getElementById("disconnect").disabled = !connected;$("#response").html();}function connect() {console.log("开始连接吧")var socket = new SockJS("http://localhost:8501/ws/public");stompClient = Stomp.over(socket);stompClient.connect({}, function (frame) {setConnected(true);console.log('Connected: ' + frame);//前端连接完成后,开始订阅主题// stompClient.subscribe('/topic/all', function (response) {stompClient.subscribe('/topic/backend/produce/summary', function (response) {var responseData = document.getElementById('responseData');var p = document.createElement('p');p.style.wordWrap = 'break-word';p.appendChild(document.createTextNode(response.body));responseData.appendChild(p);});}, {});}function disconnect() {if (stompClient != null) {stompClient.disconnect();}setConnected(false);console.log("Disconnected");}//请求地址,向WebSocket 地址发送消息function sendMsg() {var content = document.getElementById('content').value;// stompClient.send("/all", {}, JSON.stringify({'content': content}));stompClient.send("/backend/produce/summary", {}, JSON.stringify({'content': content }));}//关闭WebSocket 请求的定时任务function sendMsg1() {var content = document.getElementById('content').value;// stompClient.send("/all", {}, JSON.stringify({'content': content}));stompClient.send("/close/backend/produce/summary", {}, JSON.stringify({'content': content }));}// function sendMsg1() {//     var content = document.getElementById('content').value;//     // stompClient.send("/all", {}, JSON.stringify({'content': content}));//     stompClient.send("/close/scene/stepActualTime/128", {}, JSON.stringify({'content': content }));// }//// function sendMsg2() {//     var content = document.getElementById('content').value;//     // stompClient.send("/all", {}, JSON.stringify({'content': content}));//     stompClient.send("/close/scene/stepActualTime/219", {}, JSON.stringify({'content': content }));// }</script>
</head><body notallow="disconnect()">
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript beingenabled. Please enableJavascript and reload this page!</h2>
</noscript>
<div><div><labal>连接广播频道</labal><button id="connect" onclick="connect()">Connect</button><labal>取消连接</labal><button id="disconnect" disabled="disabled" onclick="disconnect()">Disconnect</button></div><div id="conversationDiv"><labal>广播消息</labal><input type="text" id="content"/><button id="sendMsg" onclick="sendMsg();">Send</button></div><div id="conversationDiv1"><labal>广播消息1</labal><input type="text" id="content1"/><button id="sendMsg1" onclick="sendMsg1();">Send</button></div><!--    <div id="conversationDiv2">-->
<!--        <labal>广播消息2</labal>-->
<!--        <input type="text" id="content2"/>-->
<!--        <button id="sendMsg2" onclick="sendMsg2();">Send</button>--><!--    </div>--><div><labal>接收到的消息:</labal><p id="responseData"></p></div>
</div></body>
</html>

后端启动,打开HTML测试页面,可看到运行结果!

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

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

相关文章

【史上最细教程】 Typora+PicGo+Gitee 实现发给别人的Typora笔记也能看到图片

文章目录 问题描述前提准备&#xff1a;操作步骤&#xff1a;1.Gitee新建项目作为图床、获取仓库访问密钥2.PicGo连接Gitee图床3.Typora连接PicGo 问题描述 Typora记录的笔记&#xff0c;图片保存默认在本地&#xff0c;这时候复制出来上传CSDN、或发给别人的时候图片就是空链…

HCIP实验--5

实验要求&#xff1a; 实现过程&#xff1a; &#xff08;一&#xff09;配置IP地址&#xff1a; AR1: [AR1]int g0/0/0 [AR1-GigabitEthernet0/0/0]ip add 200.1.1.1 24 Apr 3 2024 19:25:38-08:00 AR1 %%01IFNET/4/LINK_STATE(l)[0]:The line protocol IP on the interf…

C++基础13:C++输入输出

此专栏为移动机器人知识体系下的编程语言中的 C {\rm C} C从入门到深入的专栏&#xff0c;参考书籍&#xff1a;《深入浅出 C {\rm C} C》(马晓锐)和《从 C {\rm C} C到 C {\rm C} C精通面向对象编程》(曾凡锋等)。 12.C输入/输出 12.1 C流类 计算机的输入和输出是数据传送的过…

Vue.js---------Vue基础

能够说出Vue的概念和作用能够使用vue/cli脚手架工程化开发能够熟练Vue指令 一.vue基本概念 1.学习vue Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 渐进…

间接调制和带通采样!!(非奈奎斯特采样定理)

1. 直接变频 前面讲解数字调制原理时&#xff0c;直接利用IQ调制将基带信号变换为频带信号&#xff0c;这种频率变换一般被称为直接上变频&#xff1b;解调时&#xff0c;直接利用IQ解调&#xff0c;将频带信号变换回基带信号&#xff0c;这种频率变换一般被称为直接下变频。直…

vue 使用自定义标签URL Protocol 调用本地exe 并传参

创建注册表文件reg&#xff0c;并运行 里面的路径需要替换成实际exe的绝对路径 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\App] "URL:App Protocol Handler" "URL Protocol""" [HKEY_CLASSES_ROOT\App\DefaultIcon] &qu…

pycharm调试(步过(Step Over)、单步执行(Step Into)、步入(Step Into)、步出(Step Out))

pycharm调试 pycharm调试 pycharm调试为什么要学会调试&#xff1f;1. 步过 (Step Over)2. 单步执行 (Step Into)3. 步入&#xff08;Step Into&#xff09;4. 步出&#xff08;Step Out&#xff09; 为什么要学会调试&#xff1f; 调试可以帮助初学者更深入地理解编程基础&am…

【Android、 kotlin】kotlin学习笔记

基本语法 fun main(){val a2var b "Hello"println("$ (a - 1} $b Kotlin!")} Variables 只赋值一次用val read-only variables with val 赋值多次用var mutable variables with var Standard output printin() and print() functions String templ…

NKCTF2024 re VM?VM!WP

逻辑似乎很简单&#xff08;个鬼啊&#xff09; 这个函数是把输入的字符转化为二进制并倒序存储 sub_1570太大了加载不出来&#xff0c;应该是加密的主逻辑&#xff0c;目的是需要输出1 可以通过删除栈的方法强行转化伪代码 首先删掉这部分 9A0改小点 这个也是 栈这里U一下再…

DFS:深搜+回溯+剪枝解决组合问题

创作不易&#xff0c;感谢支持!!! 一、电话号码的组合 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:string hash[10]{"","","abc","def","ghi","jkl","mno","pqrs"…

爬虫部署平台crawlab使用说明

Crawlab 是一个基于 Go 语言的分布式网络爬虫管理平台&#xff0c;它支持 Python、Node.js、Jar、EXE 等多种类型的爬虫。 Crawlab 提供了一个可视化的界面&#xff0c;并且可以通过简单的配置来管理和监控爬虫程序。 以下是 Crawlab 的一些主要优点&#xff1a; 集中管理&am…

【C】leetcode力扣—— 141. 环形链表Ⅰ

目录 141. 环形链表 Ⅰ题目解题思路分析暴力求解&#xff1f;&#xff1f;快慢指针 代码 141. 环形链表 Ⅰ 题目链接: https://leetcode.cn/problems/linked-list-cycle/description/ 题目 题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某…

E-魔法猫咪(遇到过的题,做个笔记)

题解&#xff1a; 来自学长们思路&#xff1a; 其中一种正解是写单调队列。限制队列内的数单调递增&#xff0c;方法为每当新来的数据比当前队尾数据小时队 尾出列&#xff0c;直到能够插入当前值&#xff0c;这保证了队头永远是最小值。因此总体思路是队尾不断插入新值的同时 …

openlayers 入门教程(九):overlay 篇

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

vue-cli打包 nodejs内存溢出 vue2.x Last few GCs

遇到这种情况百度各种博客&#xff0c;什么改package.json里的配置&#xff0c;什么安装increase-memory-limit &#xff0c;都尝试了并没什么用处&#xff0c;最后解决方案为执行下方名单&#xff0c;再次打包就成功了&#xff1a; export NODE_OPTIONS--max_old_space_size4…

单元测试 mockito(二)

1.返回指定值 2.void返回值指定插桩 3.插桩的两种方式 when(obj.someMethod()).thenXxx():其中obj可以是mock对象 doXxx().wien(obj).someMethod():其中obj可以是mock/spy对象 spy对象在没有插桩时是调用真实方法的,写在when中会导致先执行一次原方法,达不到mock的目的&#x…

好物视频素材在哪找?视频素材大全app下载

创作优质视频内容不仅仅是一种艺术&#xff0c;也是一种科学&#xff0c;需要对素材的深刻理解和精心挑选。掌握了这些高清无水印视频素材&#xff0c;您就拥有了创作引人入胜视频内容的强大工具。以下是更多精选的视频素材网站&#xff0c;旨在为您的视频项目提供更广阔的视野…

Python | Leetcode Python题解之第10题正则表达式匹配

题目&#xff1a; 题解&#xff1a; class Solution:def isMatch(self, s: str, p: str) -> bool:m, n len(s), len(p)dp [False] * (n1)# 初始化dp[0] Truefor j in range(1, n1):if p[j-1] *:dp[j] dp[j-2]# 状态更新for i in range(1, m1):dp2 [False] * (n1) …

专升本--python运算符总结

运算优先级 同一个等级是没有先后顺序的&#xff0c;此外&#xff0c;赋值语言的先后问题&#xff1a; 赋值的顺序从上往下&#xff0c;同一行一般都是代表同时进行赋值&#xff0c;如图所示&#xff1a; 一.and A and B&#xff0c;若A,B有任意一个为假&#xff08;0&#x…

希尔排序和快排里的小区间优化

希尔排序 希尔排序是插入排序的优化。 当一串数是逆序时&#xff0c;那么每插入一个数&#xff0c;前面的数都会向后面挪动。 那么这是插入排序的时间复杂度&#xff0c;就会达到O(n^2) 希尔排序是对数组里的数进行预排序。 防止插入排序出现最坏的情况。 预排序&#xf…