当前位置: 首页 > 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 解决方案必须收集和分析不…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...