当前位置: 首页 > news >正文

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.数据链路层 用于两个设备&#xff08;同一种数据链路节点&#xff09;之间进行传递 6.以太网ether 7.局域网通…...

java面试题-Redis相关面试题

Redis相关面试题 面试官&#xff1a;什么是缓存穿透 ? 怎么解决 ? 候选人&#xff1a; 嗯~~&#xff0c;我想一下 缓存穿透是指查询一个一定不存在的数据&#xff0c;如果从存储层查不到数据则不写入缓存&#xff0c;这将导致这个不存在的数据每次请求都要到 DB 去查询&…...

你用过 Maven Shade 插件吗?

文章首发地址 Maven Shade插件是Maven构建工具的一个插件&#xff0c;用于构建可执行的、可独立运行的JAR包。它解决了依赖冲突的问题&#xff0c;将项目及其所有依赖&#xff08;包括传递依赖&#xff09;合并到一个JAR文件中。 下面是对Maven Shade插件的一些详解&#xff…...

Android 后台启动Activity适配

在Android 9及以下版本&#xff0c;后台启动Activity相对自由&#xff0c;但是如果在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报错

最近在写一个后台&#xff0c;需要在表格中多选&#xff0c;然后点击编辑按钮的时候&#xff0c;需要回显已经选中的表单项 <el-table v-loading"loading" :data"discountList" :row-key"(row) > row.id" refmultipleTable selection-cha…...

Ubuntu18.04系统下通过ROS控制Kinova真实机械臂-多种实现方式

所用测试工作空间test_ws&#xff1a;包含官网最原始的功能包 一、使用Kinova官方Development center控制真实机械臂 0.在ubuntu系统安装Kinova机械臂的Development center&#xff0c;这一步自行安装&#xff0c;很简单。 1.使用USB连接机械臂和电脑 2.Development center…...

聊聊如何玩转spring-boot-admin

前言 1、何为spring-boot-admin&#xff1f; Spring Boot Admin 是一个监控工具&#xff0c;旨在以良好且易于访问的方式可视化 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中&#xff0c;我们常在用户注册账户时发送确认邮件&#xff0c;或是推送信息。邮件必要的字段包含发信方(sender)&#xff0c;收信方(to)&#xff0c;邮件主题…...

ChatGPT追祖寻宗:GPT-1论文要点解读

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

回归拟合 | 灰狼算法优化核极限学习机(GWO-KELM)MATLAB实现

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

Mysql JSON

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

使用Vue + axios实现图片上传,轻松又简单

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

C# 中什么是重写(子类改写父类方法)

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

【Leetcode-面试经典150题-day22】

目录 97. 交错字符串 97. 交错字符串 题意&#xff1a; 给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 …...

LDAP服务器如何重启

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

AP51656 LED车灯电源驱动IC 兼容替代PT4115 PT4205 PWM和线性调光

产品描述 AP51656是一款连续电感电流导通模式的降压恒流源 用于驱动一颗或多颗串联LED 输入电压范围从 5V 到 60V&#xff0c;输出电流 可达 1.5A 。根据不同的输入电压和 外部器件&#xff0c; 可以驱动高达数十瓦的 LED。 内置功率开关&#xff0c;采用高端电流采样设置 …...

浅析安防视频监控平台EasyCVR视频融合平台接入大量设备后是如何维持负载均衡的

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

SIEM 中不同类型日志监控及分析

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

【java基础复习】java中的数组在内存中是如何存储的?

基本数据类型与内存存储数组类型与内存存储为什么数组需要两块空间&#xff1f;感谢 &#x1f496; 基本数据类型与内存存储 首先&#xff0c;让我们回顾一下基本数据类型的内存存储方式。对于一个基本类型变量&#xff0c;例如int类型的变量a&#xff0c;内存中只有一块内存空…...

MySQL数据库 MHA高可用

MySQL MHA 什么是 MHA MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换的…...

leetcode669. 修剪二叉搜索树(java)

修剪二叉搜索树 题目描述递归代码演示&#xff1a; 题目描述 难度 - 中等 LC - 669. 修剪二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[low, high]中。修剪树 不应该 改变保留…...

计算机网络的故事——确认访问用户身份的认证

确认访问用户身份的认证 HTTP使用的认证方式&#xff1a;BASIC认证&#xff08;基本认证&#xff09;、DIGEST&#xff08;摘要认证&#xff09;、SSL客户端认证、FormBase认证&#xff08;基于表单认证&#xff09;。 基于表单的认证&#xff1a;涉及到session管理以及cookie…...

C#禁用或启用任务管理器

参考文档https://zhuanlan.zhihu.com/p/95156063 借助上述参考文档里的C#操作注册表类&#xff0c;禁用或启用任务管理器 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace HideTaskMgr { class Program { …...

【Redis】NoSQL之Redis的配置及优化

关系数据库与非关系数据库 关系型数据库 关系型数据库是一个结构化的数据库&#xff0c;创建在关系模型&#xff08;二维表格模型&#xff09;基础上&#xff0c;一般面向于记录。 SQL 语句&#xff08;标准数据查询语言&#xff09;就是一种基于关系型数据库的语言&a…...

【数据库】如何利用Python中的petl将PostgreSQL中所有表的外键删除,迁移数据,再重建外键

一、简介 在数据库管理中&#xff0c;外键是一种重要的约束&#xff0c;用于确保数据的一致性和完整性。然而&#xff0c;在某些情况下&#xff0c;我们可能需要删除或修改外键。本文将介绍如何使用Python中的petl库将PostgreSQL中所有表的外键删除&#xff0c;迁移数据&#…...

Si24R2F+畜牧 耳标测体温开发资料

Si24R2F是针对IOT应用领域推出的新款超低功耗2.4G内置NVM单发射芯片。广泛应用于2.4G有源活体动物耳标&#xff0c;带实时测温计步功能。相较于Si24R2E&#xff0c;Si24R2F增加了温度监控、自动唤醒间隔功能&#xff1b;发射功率由7dBm增加到12dBm&#xff0c;距离更远&#xf…...

阿里云服务器退款流程_退订入口_到账时间说明

阿里云服务器如何退款&#xff1f;云服务器在哪申请退款&#xff1f;在用户中心订单管理中的退订管理中退款&#xff0c;阿里云百科分享阿里云服务器退款流程&#xff0c;包括申请退款入口、云服务器退款限制条件、退款多久到账等详细说明&#xff1a; 目录 阿里云服务器退款…...

自然语言处理实战项目17-基于多种NLP模型的诈骗电话识别方法研究与应用实战

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理实战项目17-基于NLP模型的诈骗电话识别方法研究与应用&#xff0c;相信最近小伙伴都都看过《孤注一掷》这部写实的诈骗电影吧&#xff0c;电影主要围绕跨境网络诈骗展开&#xff0c;电影取材自上万起真…...