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 解决方案必须收集和分析不…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...