SpringBoot / Vue 对SSE的基本使用(简单上手)
一、SSE是什么?
SSE技术是基于单工通信模式,只是单纯的客户端向服务端发送请求,服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放,等数据更新的时候才返回给客户端,当客户端接收到消息后,再向服务端发送请求,周而复始。
注意:因为EventSource对象是SSE的客户端,可能会有浏览器对其不支持
二、sse 与 websoket
SSE(Server-Sent Events)
是 HTML5 遵循 W3C 标准提出的客户端和服务端之间进行实时通信的协议。
优点
- SSE 客户端可以接收来自服务器的“流”数据,而不需要进行轮询。由于没有浪费的请求,因此 SSE 对于减轻服务器的压力非常有用。
- SSE 使用纯 JavaScript 实现简单,不需要额外的插件或库来处理消息。客户端可以使用 EventSource 接口轻松地与 SSE 服务器通信。
- SSE 天生具有自适应性,由于 SSE 是基于 HTTP 响应使用 EventStream 传递消息,因此它利用了 HTTP 的开销和互联网上的结构。
- SSE 可以与任何服务器语言和平台一起使用,因为 SSE 是一种规定了消息传递方式的技术,不依赖于具体的服务器语言和平台。
缺点
- SSE 是单向通信,只能从服务器推送到客户端。如果应用程序需要双向通信,就需要使用 Websocket。
- SSE无法发送二进制数据,只能发送 UTF-8 编码的文本。如果应用程序需要发送二进制数据,就需要使用 Websocket。
- SSE 不是所有浏览器都支持。虽然 SSE 是 HTML5 的一部分,但具体的浏览器支持性可能会有差异。
Websocket
是 HTML5 的一部分,提供了一种双向通信的机制。
优点
- Websocket 支持双向通信。使用 Websocket 可以同时向客户端发送和接收数据。
- Websocket 协议可以传输二进制数据,这使得 Websocket 更加灵活和强大。
- Websocket 连接长期存在,而不需要仅仅为了接收数据而保持 HTTP 连接打开。
- Websocket 的实现支持跨域的通信,可以方便地进行跨域通信。
缺点
- Websocket 不支持所有浏览器。特别是老浏览器可能不支持 Websocket 协议。
- Websocket 是一种全双工的通信方式。由于 Websocket 长期存在,会占用服务器资源。在高并发场景下,应该考虑使用 SSE。
三、前端示例代码:
// 建立连接createSseConnect(clientId){if(window.EventSource){const eventSource = new EventSource('http://127.0.0.1:8083/sse/createSseConnect?clientId='+clientId);console.log(eventSource)eventSource.onmessage = (event) =>{console.log("onmessage:"+clientId+": "+event.data)};eventSource.onopen = (event) =>{console.log("onopen:"+clientId+": "+event)};eventSource.onerror = (event) =>{console.log("onerror :"+clientId+": "+event)};eventSource.close = (event) =>{console.log("close :"+clientId+": "+event)};}else{console.log("你的浏览器不支持SSE~")}console.log(" 测试 打印")
},
四、后端示例代码:
SseController
package com.joker.cloud.linserver.controller;import com.joker.cloud.linserver.conf.sse.sseUtils;
import com.joker.common.message.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.Map;/*** SseController** @author joker* @version 1.0* 2023/8/9 11:18**/
@RestController
@Slf4j
@CrossOrigin
@RequestMapping("/sse")
public class SseController {@Autowiredprivate sseUtils sseUtils;@GetMapping(value = "/createSseConnect", produces="text/event-stream;charset=UTF-8")public SseEmitter createSseConnect(@RequestParam(name = "clientId", required = false) Long clientId) {return sseUtils.connect(clientId);}@PostMapping("/sendMessage")public void sendMessage(@RequestParam("clientId") Long clientId, @RequestParam("message") String message){sseUtils.sendMessage(clientId, "123456789", message);}@GetMapping(value = "/listSseConnect")public Result<Map<Long, SseEmitter>> listSseConnect(){Map<Long, SseEmitter> sseEmitterMap = sseUtils.listSseConnect();return Result.success(sseEmitterMap);}/*** 关闭SSE连接** @param clientId 客户端ID**/@GetMapping("/closeSseConnect")public Result closeSseConnect(Long clientId) {sseUtils.deleteUser(clientId);return Result.success();}}
sseUtils工具类
package com.joker.cloud.linserver.conf.sse;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;/*** sseUtils** @author joker* @version 1.0* 2023/8/9 11:20**/
@Slf4j
@Component
public class sseUtils {private static final Map<Long, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();/*** 创建连接*/public SseEmitter connect(Long userId) {if (sseEmitterMap.containsKey(userId)) {SseEmitter sseEmitter =sseEmitterMap.get(userId);sseEmitterMap.remove(userId);sseEmitter.complete();}try {UUID uuid = UUID.randomUUID();String str = uuid.toString();String temp = str.substring(0, 8) + str.substring(9, 13) + str.substring(14, 18) + str.substring(19, 23) + str.substring(24);// 设置超时时间,0表示不过期。默认30秒SseEmitter sseEmitter = new SseEmitter(30*1000L);sseEmitter.send(SseEmitter.event().id(temp).data(""));
// reconnectTime(10*1000L)// 注册回调sseEmitter.onCompletion(completionCallBack(userId));
// sseEmitter.completeWithError(errorCallBack(userId));sseEmitter.onTimeout(timeoutCallBack(userId));sseEmitterMap.put(userId, sseEmitter);log.info("创建sse连接完成,当前用户:{}", userId);return sseEmitter;} catch (Exception e) {log.info("创建sse连接异常,当前用户:{}", userId);}return null;}/*** 给指定用户发送消息**/public boolean sendMessage(Long userId,String messageId, String message) {if (sseEmitterMap.containsKey(userId)) {SseEmitter sseEmitter = sseEmitterMap.get(userId);try {sseEmitter.send(SseEmitter.event().id(messageId).data(message));
// reconnectTime(10*1000L)log.info("用户{},消息id:{},推送成功:{}", userId,messageId, message);return true;}catch (Exception e) {sseEmitterMap.remove(userId);log.info("用户{},消息id:{},推送异常:{}", userId,messageId, e.getMessage());sseEmitter.complete();return false;}}else {log.info("用户{}未上线", userId);}return false;}/*** 删除连接* @param userId*/public void deleteUser(Long userId){removeUser(userId);}private static Runnable completionCallBack(Long userId) {return () -> {log.info("结束sse用户连接:{}", userId);removeUser(userId);};}private static Throwable errorCallBack(Long userId) {log.info("sse用户连接异常:{}", userId);removeUser(userId);return new Throwable();}private static Runnable timeoutCallBack(Long userId) {return () -> {log.info("连接sse用户超时:{}", userId);removeUser(userId);};}/*** 断开* @param userId*/public static void removeUser(Long userId){if (sseEmitterMap.containsKey(userId)) {SseEmitter sseEmitter = sseEmitterMap.get(userId);sseEmitterMap.remove(userId);sseEmitter.complete();}else {log.info("用户{} 连接已关闭",userId);}}public Map<Long, SseEmitter> listSseConnect(){return sseEmitterMap;}
}
五、模拟测试:
模拟浏览器发送建立连接的请求:

切换到时间栏目,可以看到长连接始终保持着的:

切换到eventStream:可以看到后端通信的streams流数据

使用postMan 模拟后端服务器推送给客户端消息

浏览器建立的连接中会看到服务器推送到客户端的消息内容及ID等基础信息

控制台也可以监听到事件的变化并输出

相关文章:
SpringBoot / Vue 对SSE的基本使用(简单上手)
一、SSE是什么? SSE技术是基于单工通信模式,只是单纯的客户端向服务端发送请求,服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放,等数据更新的时候才返回给客户端,当客户端接收到消息后,…...
Qt串口基本设置与协议收发
前言 1.一直都想要做一个Qt上位机,趁着这个周末有时间,动手写一下 2.comboBox没有点击的信号,所以做了一个触发的功能 3.Qt的数据类型很奇怪,转来转去的我也搞得很迷糊 4.给自己挖个坑,下一期做一个查看波形的上位…...
interview3-微服务与MQ
一、SpringCloud篇 (1)服务注册 常见的注册中心:eureka、nacos、zookeeper eureka做服务注册中心: 服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、…...
kafka详解一
kafka详解一 1、消息引擎背景 根据维基百科的定义,消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息,实现松耦合的异步式数据传递. 即:系统 A 发送消息给消息引擎系统,系统 B 从消息引擎系统中读取 A…...
Flutter yuv 转 rgb
1、引用yuv_converter库 yuv_converter: ^0.0.1 2、导入头文件: import package:yuv_converter/yuv_converter.dart;3、yuv转rgb YuvConverter.yuv420NV21ToRgba8888(yuvRawData, 512, 512) 根据yuv格式选择不同的api。 举个例子: void initState() …...
MySQL——子查询
2023.9.8 相关学习笔记: #子查询 /* 含义: 出现在其他语句中的select语句,称为子查询或内查询 外部的查询语句,称为主查询或外查询分类: 按子查询出现的位置:select后面:仅仅支持标量子查询fro…...
Java学习笔记---多态
面向对象三大特征之一(继承,封装,多态) 多态的应用场景:根据传递对象的不同,调用不同的show方法 一、多态的定义 同类型的对象,表现出的不同形态(对象的多种形态) 二…...
2023-09-10 LeetCode每日一题(课程表 II)
2023-09-10每日一题 一、题目编号 210. 课程表 II二、题目链接 点击跳转到题目位置 三、题目描述 现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] [ai, bi] ,表示在…...
合并区间【贪心算法】
合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 class Solution {public int[][] merge(int[…...
2023,软件测试人的未来在哪里?
2023年,IT行业出现空前的萧条,首先是年初一开始各大厂像着了魔似的不约而同的纷纷裁员、降薪、奖金包缩水,随之而来的是需求萎缩,HC减少或封锁等等。 而有幸未被列入裁员名单的在职人员,庆幸之余也心有余悸࿰…...
Python中的Numpy向量计算(R与Python系列第三篇)
目录 一、什么是Numpy? 二、如何导入NumPy? 三、生成NumPy数组 3.1利用序列生成 3.2使用特定函数生成NumPy数组 (1)使用np.arange() (2)使用np.linspace() 四、NumPy数组的其他常用函数 (1)np.z…...
LeetCode刷题笔记【27】:贪心算法专题-5(无重叠区间、划分字母区间、合并区间)
文章目录 前置知识435. 无重叠区间题目描述参考<452. 用最少数量的箭引爆气球>, 间接求解直接求"重叠区间数量" 763.划分字母区间题目描述贪心 - 建立"最后一个当前字母"数组优化marker创建的过程 56. 合并区间题目描述解题思路代码① 如果有重合就合…...
nvidia-smi 命令详解
nvidia-smi 命令详解 1. nvidia-smi 面板解析2. 显存与GPU的区别 Reference: nvidia-smi命令详解 相关文章: nvidia-smi nvcc -V 及 CUDA、cuDNN 安装 nvidia-smi(NVIDIA System Management Interface) 是一种命令行实用程序,用于监控和管理 NVIDIA G…...
fork()函数的返回值
在程序中,int pd fork() 是一个典型的 fork() 调用。fork() 函数会创建一个新的进程,然后在父进程中返回子进程的进程ID(PID),在子进程中返回0。所以 pd 的值会根据当前进程是父进程还是子进程而有所不同:…...
Stable Diffusion WebUI挂VPN不能跑图解决办法(Windows)
如何解决SD在打开VPN的状态不能运行的问题 在我们开VPN的时候会出现无法生成图片,也无法做其他任何事,这个时候是不是很着急呢? 别急,我这里会说明如何解决。 就像这样,运行半天生成不了图,有时还会出现…...
Android的本地数据
何为本地,即写完之后除非手动修改,否像嘎了一样在那固定死了 有些需求可能也会要求我们去写死数据,因为这需求是一成不变的,那么你通常会用什么方法写死呢? 1. 本地存储-SharedPreferences 此方法可以长时间保存于手…...
android NDK 开发包,网盘下载,不限速
记录下ndk 开发包的地址,分享给大家。 另外有Android studio的下载包, 在另一篇文章 链接:http://t.csdn.cn/JSr9x Android Studio.exe 下载 2023 最新更新,网盘下载_hsj-obj的博客-CSDN博客 主要是19-25,其他的没有…...
【每日一题Day320】LC2651计算列车到站时间 | 数学
计算列车到站时间【LC2651】](https://leetcode.cn/problems/calculate-delayed-arrival-time/) 给你一个正整数 arrivalTime 表示列车正点到站的时间(单位:小时),另给你一个正整数 delayedTime 表示列车延误的小时数。 返回列车实…...
C语言柔性数组详解:让你的程序更灵活
柔性数组 一、前言二、柔性数组的用法三、柔性数组的内存分布四、柔性数组的优势五、总结 一、前言 仔细观察下面的代码,有没有看出哪里不对劲? struct S {int i;double d;char c;int arr[]; };还有另外一种写法: struct S {int i;double …...
Redis-带你深入学习数据类型list
目录 1、list列表 2、list相关命令 2.1、添加相关命令:rpush、lpush、linsert 2.2、查找相关命令:lrange、lindex、llen 2.3、删除相关命令:lpop、rpop、lrem、ltrim 2.4、修改相关命令:lset 2.5、阻塞相关命令:…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
跨平台商品数据接口的标准化与规范化发展路径:淘宝京东拼多多的最新实践
在电商行业蓬勃发展的当下,多平台运营已成为众多商家的必然选择。然而,不同电商平台在商品数据接口方面存在差异,导致商家在跨平台运营时面临诸多挑战,如数据对接困难、运营效率低下、用户体验不一致等。跨平台商品数据接口的标准…...
云原生安全实战:API网关Envoy的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口,负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...
Python的__call__ 方法
在 Python 中,__call__ 是一个特殊的魔术方法(magic method),它允许一个类的实例像函数一样被调用。当你在一个对象后面加上 () 并执行时(例如 obj()),Python 会自动调用该对象的 __call__ 方法…...
简约商务通用宣传年终总结12套PPT模版分享
IOS风格企业宣传PPT模版,年终工作总结PPT模版,简约精致扁平化商务通用动画PPT模版,素雅商务PPT模版 简约商务通用宣传年终总结12套PPT模版分享:商务通用年终总结类PPT模版https://pan.quark.cn/s/ece1e252d7df...
生产管理系统开发:专业软件开发公司的实践与思考
生产管理系统开发的关键点 在当前制造业智能化升级的转型背景下,生产管理系统开发正逐步成为企业优化生产流程的重要技术手段。不同行业、不同规模的企业在推进生产管理数字化转型过程中,面临的挑战存在显著差异。本文结合具体实践案例,分析…...
