SSE 和 WebSocket 应用
SSE 和 WebSocket 应用
- 一.SSE 和 WebSocket 对比
- 二.SSE 和 WebSocket 调试
- SpringBoot 下 SSE 应用
- 1.依赖
- 2.启动类
- 3.接口类
- 4.Html 测试
- 5.测试结果
- SpringBoot 下 WebSocket 应用
- 1.依赖
- 2.启动类
- 3.WS 切点配置
- 4.WS连接类配置
- 5.WS Html 测试
- 6.测试结果
一.SSE 和 WebSocket 对比
SSE 全称 Server-Send Events 基于 HTTP 的单向通信协议
WebSocket 基于 HTTP 封装的 WS 双向通信协议
二.SSE 和 WebSocket 调试
JVM 版本
SpringBoot 下 SSE 应用
1.依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>socket-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>20</maven.compiler.source><maven.compiler.target>20</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.version>3.1.3</spring.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version></dependency></dependencies></project>
2.启动类
package org.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author Administrator*/
@SpringBootApplication
public class SocketDemoApp {public static void main(String[] args) {SpringApplication.run(SocketDemoApp.class,args);}
}
3.接口类
package org.example.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;/*** @author zhuwd && moon* @Description* @create 2023-09-04 22:53*/
@Slf4j
@CrossOrigin
@RestController
@RequestMapping("/test")
public class TestController {/*** 线程副本变量*/private ThreadLocal<AtomicReference<Boolean>> isSendThreadLocal = new ThreadLocal<>();/*** 缓存线程*/private Map<Integer,AtomicReference<Boolean>> sidThreadMap = new ConcurrentHashMap<>();/*** Server Send Event** @param type 1 温度 2 湿度* @return*/@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public synchronized SseEmitter sendToClient(int sid,int type){SseEmitter emitter = new SseEmitter(1000000L);// 模拟生成实时股票价格并推送给客户端Random random = new Random();new Thread(() -> {try {//缓存当前线程AtomicReference temp = sidThreadMap.remove(sid);if (null != temp){temp.set(false);}//添加缓存sidThreadMap.put(sid,new AtomicReference<>(false));//缓存状态isSendThreadLocal.set(sidThreadMap.get(sid));//发数while (true) {try {// 生成随机值double val = 10 + random.nextDouble() * 10;// 获取单位String unit = type == 1 ? "℃":(type == 2?"%":"");// 构造股票价格的消息String message = String.format("%.2f", val) + unit;// 发送消息给客户端emitter.send(SseEmitter.event().data(message));// 休眠 1 秒钟Thread.sleep(1000);} catch (IOException e) {throw new RuntimeException(e);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {//判断是否退出if (isSendThreadLocal.get().get()){isSendThreadLocal.remove();break;}}}} catch (Exception e) {emitter.completeWithError(e);}}).start();//返回return emitter;}/*** Stop Server Send Event* @param sid*/@GetMapping("/stop")public void stop(int sid){//取出线程AtomicReference<Boolean> temp = sidThreadMap.remove(sid);if (null != temp){temp.set(true);}}}
4.Html 测试
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>温湿度监控</title>
</head>
<body>
<h1>温度</h1>
<h3><div id="temperature"></div></h3>
<h1>湿度</h1>
<h3><div id="humidity"></div></h3><br/>
<button onclick="stop(1,'temperature')">停止温度监控</button>
<button onclick="stop(2,'humidity')">停止湿度监控</button>
<button onclick="start(1)">恢复温度监控</button>
<button onclick="start(2)">恢复湿度监控</button>
<!-- <button οnclick="console.log(map.get(1).readyState)">恢复湿度监控</button> --><script src="https://code.jquery.com/jquery-3.6.1.js"></script>
<script>const map = new Map();//EventSource 事件 onopen/onmessage/onerrorfunction getSSE(sid,type,id){obj = new EventSource('http://127.0.0.1:8080/test/sse?sid=' + sid + '&type=' + type);obj.onmessage = function (event) {document.getElementById(id).innerHTML = event.data;};document.getElementById(id).style.color = "#00FA9A";//"#ff0000";obj.onopen = function (){console.log('obj opopen connect obj state:' + obj.readyState)}obj.onerror = function (){obj.close();console.log('obj onerror connect obj state:' + obj.readyState)}map.set(sid,obj)}function init() {getSSE(1,1,'temperature')getSSE(2,2,'humidity')}init();/*** 停止*/function stop(sid,id){$.get("http://127.0.0.1:8080/test/stop?sid=" + sid,function(data,status){console.log("Data: " + data + "\nStatus: " + status);document.getElementById(id).style.color = "#ff0000";map.get(sid).close();});}/*** 开始*/function start(sid){status = map.get(sid).readyStateif (status == 1) {console.log('see is on connected...' + sid + ' status ' + status)return}if (sid == 1) {getSSE(1,1,'temperature')} else if (sid == 2) {getSSE(2,2,'humidity')}}</script>
</body>
</html>
5.测试结果
SpringBoot 下 WebSocket 应用
1.依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>socket-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>20</maven.compiler.source><maven.compiler.target>20</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.version>3.1.3</spring.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>${spring.version}</version></dependency></dependencies></project>
2.启动类
package org.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author Administrator*/
@SpringBootApplication
public class SocketDemoApp {public static void main(String[] args) {SpringApplication.run(SocketDemoApp.class,args);}
}
3.WS 切点配置
package org.example.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @author zhuwd && moon* @Description* @create 2023-09-04 23:15*/
@Configuration
public class WebSocketConfig {/*** 注入 ServerEndpointExporter* 这个 Bean 会自动注册使用了 @ServerEndpoint 声明的 Websocket Endpoint*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
4.WS连接类配置
package org.example.config;import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;/*** @author zhuwd && moon** 接口路径 ws://localhost:8080/webSocket/{sid};** @Description* @create 2023-09-04 23:16*/@Slf4j
@Component
@ServerEndpoint("/websocket/{sid}")
public class MyWebSocket {/*** ws 会话*/private Session session;/*** 连接 id*/private String sid;/*** 缓存类对象*/private static CopyOnWriteArraySet<MyWebSocket> webSockets = new CopyOnWriteArraySet<>();/*** 缓存用户信息*/private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<>();/*** 缓存用户信息*/private static ConcurrentHashMap<Session,String> sidPool = new ConcurrentHashMap<>();/*** 链接成功调用的方法* @param session* @param sid*/@OnOpenpublic void onOpen(Session session, @PathParam(value="sid") String sid) {try {this.session = session;this.sid = sid;webSockets.add(this);sessionPool.put(sid, session);sidPool.put(session,sid);log.info("one client join in sid {} all counts {}",sid,webSockets.size());} catch (Exception e) {log.error("join in error:",e);}}/*** 链接关闭调用的方法*/@OnClosepublic void onClose() {try {webSockets.remove(this);sessionPool.remove(this.sid);log.info("one client leave sid {} all counts {}",sid,webSockets.size());} catch (Exception e) {log.error("leave error:",e);}}/*** 收到客户端消息后调用的方法** @param message* @param session*/@OnMessagepublic void onMessage(Session session,String message) {//发送sendOneMessage(sid,"Hello " + message);log.info("client sid {} message : {}",sidPool.get(session),message);}/** 发送错误时的处理* @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("ws sid {} error:",sidPool.get(session),error);}/*** 广播* @param message*/public void sendAllMessage(String message) {log.info("broadcast : {}",message);for(MyWebSocket webSocket : webSockets) {try {if(webSocket.session.isOpen()) {webSocket.session.getAsyncRemote().sendText(message);}} catch (Exception e) {e.printStackTrace();}}}/*** 单点发送* @param sid* @param message*/public void sendOneMessage(String sid, String message) {Session session = sessionPool.get(sid);if (session != null && session.isOpen()) {try {log.info("to one : {}",message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}/*** 多点发送* @param sids* @param message*/public void sendMoreMessage(String[] sids, String message) {for(String sid:sids) {Session session = sessionPool.get(sid);if (session != null&&session.isOpen()) {try {log.info("to more : {}",message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}}
}
5.WS Html 测试
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>WS</title>
</head>
<body>
<h1>Message</h1>
<h3><div><input id="msg" type="text" /></div></h3>
<h1>Receive</h1>
<h3><div><input id="rsg" type="text" readonly = true/></div></h3>
<br/><button onclick="send()">发送</button><script src="https://code.jquery.com/jquery-3.6.1.js"></script>
<script>var socket = new WebSocket('ws://127.0.0.1:8080/websocket/1');socket.onopen = function(evt){};socket.onerror = function(evt){};socket.onmessage = function(evt){console.log('------' + evt.data)$('#rsg').val(evt.data)};socket.onclose = function(evt){};function send(){socket.send($('#msg').val());}</script>
</body>
</html>
6.测试结果
相关文章:

SSE 和 WebSocket 应用
SSE 和 WebSocket 应用 一.SSE 和 WebSocket 对比二.SSE 和 WebSocket 调试SpringBoot 下 SSE 应用1.依赖2.启动类3.接口类4.Html 测试5.测试结果 SpringBoot 下 WebSocket 应用1.依赖2.启动类3.WS 切点配置4.WS连接类配置5.WS Html 测试6.测试结果 一.SSE 和 WebSocket 对比 …...

mac帧 arp
1.分片 2.MSS max segment size 3.跨网络的本质 就是经历很多的子网或者局域网 4.将数据从A主机跨网络送到B主机的能力 IP和mac IP解决的是路径选择的问题 5.数据链路层 用于两个设备(同一种数据链路节点)之间进行传递 6.以太网ether 7.局域网通…...
java面试题-Redis相关面试题
Redis相关面试题 面试官:什么是缓存穿透 ? 怎么解决 ? 候选人: 嗯~~,我想一下 缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询&…...

你用过 Maven Shade 插件吗?
文章首发地址 Maven Shade插件是Maven构建工具的一个插件,用于构建可执行的、可独立运行的JAR包。它解决了依赖冲突的问题,将项目及其所有依赖(包括传递依赖)合并到一个JAR文件中。 下面是对Maven Shade插件的一些详解ÿ…...
Android 后台启动Activity适配
在Android 9及以下版本,后台启动Activity相对自由,但是如果在Activity上下文之外启动Activity会有限制。 Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag所以此时需要给intent添加flag&#x…...

使用element-ui中的el-table回显已选中数据时toggleRowSelection报错
最近在写一个后台,需要在表格中多选,然后点击编辑按钮的时候,需要回显已经选中的表单项 <el-table v-loading"loading" :data"discountList" :row-key"(row) > row.id" refmultipleTable selection-cha…...

Ubuntu18.04系统下通过ROS控制Kinova真实机械臂-多种实现方式
所用测试工作空间test_ws:包含官网最原始的功能包 一、使用Kinova官方Development center控制真实机械臂 0.在ubuntu系统安装Kinova机械臂的Development center,这一步自行安装,很简单。 1.使用USB连接机械臂和电脑 2.Development center…...

聊聊如何玩转spring-boot-admin
前言 1、何为spring-boot-admin? Spring Boot Admin 是一个监控工具,旨在以良好且易于访问的方式可视化 Spring Boot Actuators 提供的信息 快速开始 如何搭建spring-boot-admin-server 1、在服务端项目的POM引入相应的GAV <dependency><grou…...

rocky(centos) 安装redis,并设置开机自启动
一、下载并安装 1、官网下载Redis 并安装 Download | RedisRedisYou can download the last Redis source files here. For additional options, see the Redis downloads section below.Stable (7.2)Redis 7.2 …https://redis.io/download/ 2、上传下载好的redis压缩包到 /…...

Flask狼书笔记 | 06_电子邮件
文章目录 6 电子邮件6.1 使用Flask-Mail发送6.2 使用事务邮件服务SendGrid6.3 电子邮件进阶6.4 小结 6 电子邮件 Web中,我们常在用户注册账户时发送确认邮件,或是推送信息。邮件必要的字段包含发信方(sender),收信方(to),邮件主题…...

ChatGPT追祖寻宗:GPT-1论文要点解读
论文地址:《Improving Language Understanding by Generative Pre-Training》 最近一直忙着打比赛,好久没更文了。这两天突然想再回顾一下GPT-1和GPT-2的论文, 于是花时间又整理了一下,也作为一个记录~话不多说,让我们…...

回归拟合 | 灰狼算法优化核极限学习机(GWO-KELM)MATLAB实现
这周有粉丝私信想让我出一期GWO-KELM的文章,因此乘着今天休息就更新了(希望不算晚) 作者在前面的文章中介绍了ELM和KELM的原理及其实现,ELM具有训练速度快、复杂度低、克服了传统梯度算法的局部极小、过拟合和学习率的选择不合适等优点,而KEL…...

Mysql JSON
select json_extract(c2, $.a) select c2->"$.a" // json_extract的语法糖 (取出的值会保留"双引号" so不适合实战) 注:mysql若是引擎Mariadb则不支持json操作符->>语法糖 select c2->…...

使用Vue + axios实现图片上传,轻松又简单
目录 一、Vue框架介绍 二、Axios 介绍 三、实现图片上传 四、Java接收前端图片 一、Vue框架介绍 Vue是一款流行的用于构建用户界面的开源JavaScript框架。它被设计用于简化Web应用程序的开发,特别是单页面应用程序。 Vue具有轻量级、灵活和易学的特点…...

C# 中什么是重写(子类改写父类方法)
方法重写是指在继承关系中,子类重新实现父类或基类的某个方法。这种方法允许子类根据需要修改或扩展父类或基类的方法功能。在面向对象编程中,方法重写是一种多态的表现形式,它使得子类可以根据不同的需求和场景提供不同的方法实现。 方法重…...

【Leetcode-面试经典150题-day22】
目录 97. 交错字符串 97. 交错字符串 题意: 给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串: s s1 s2 …...

LDAP服务器如何重启
1、find / -name ldap 该命令只会从根路径下查看ldap文件夹 find / -name ldap2、该命令会从根路径/查看所有包含ldap路径的文件夹,会查询出所有,相当于全局查询 find / -name *ldap*2、启动OpenLADP 找到LDAP安装目录后,执行以下命令 #直…...

AP51656 LED车灯电源驱动IC 兼容替代PT4115 PT4205 PWM和线性调光
产品描述 AP51656是一款连续电感电流导通模式的降压恒流源 用于驱动一颗或多颗串联LED 输入电压范围从 5V 到 60V,输出电流 可达 1.5A 。根据不同的输入电压和 外部器件, 可以驱动高达数十瓦的 LED。 内置功率开关,采用高端电流采样设置 …...

浅析安防视频监控平台EasyCVR视频融合平台接入大量设备后是如何维持负载均衡的
安防视频监控平台EasyCVR视频融合平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。视频汇聚融合管理平台EasyCVR既具备…...

SIEM 中不同类型日志监控及分析
安全信息和事件管理(SIEM)解决方案通过监控来自网络的不同类型的数据来确保组织网络的健康安全状况,日志数据记录设备上发生的每个活动以及整个网络中的应用程序,若要评估网络的安全状况,SIEM 解决方案必须收集和分析不…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...