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

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是什么&#xff1f; SSE技术是基于单工通信模式&#xff0c;只是单纯的客户端向服务端发送请求&#xff0c;服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放&#xff0c;等数据更新的时候才返回给客户端&#xff0c;当客户端接收到消息后&#xff0c;…...

Qt串口基本设置与协议收发

前言 1.一直都想要做一个Qt上位机&#xff0c;趁着这个周末有时间&#xff0c;动手写一下 2.comboBox没有点击的信号&#xff0c;所以做了一个触发的功能 3.Qt的数据类型很奇怪&#xff0c;转来转去的我也搞得很迷糊 4.给自己挖个坑&#xff0c;下一期做一个查看波形的上位…...

interview3-微服务与MQ

一、SpringCloud篇 &#xff08;1&#xff09;服务注册 常见的注册中心&#xff1a;eureka、nacos、zookeeper eureka做服务注册中心&#xff1a; 服务注册&#xff1a;服务提供者需要把自己的信息注册到eureka&#xff0c;由eureka来保存这些信息&#xff0c;比如服务名称、…...

kafka详解一

kafka详解一 1、消息引擎背景 根据维基百科的定义&#xff0c;消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息&#xff0c;实现松耦合的异步式数据传递. 即&#xff1a;系统 A 发送消息给消息引擎系统&#xff0c;系统 B 从消息引擎系统中读取 A…...

Flutter yuv 转 rgb

1、引用yuv_converter库 yuv_converter: ^0.0.1 2、导入头文件&#xff1a; import package:yuv_converter/yuv_converter.dart;3、yuv转rgb YuvConverter.yuv420NV21ToRgba8888(yuvRawData, 512, 512) 根据yuv格式选择不同的api。 举个例子&#xff1a; void initState() …...

MySQL——子查询

2023.9.8 相关学习笔记&#xff1a; #子查询 /* 含义&#xff1a; 出现在其他语句中的select语句&#xff0c;称为子查询或内查询 外部的查询语句&#xff0c;称为主查询或外查询分类&#xff1a; 按子查询出现的位置&#xff1a;select后面&#xff1a;仅仅支持标量子查询fro…...

Java学习笔记---多态

面向对象三大特征之一&#xff08;继承&#xff0c;封装&#xff0c;多态&#xff09; 多态的应用场景&#xff1a;根据传递对象的不同&#xff0c;调用不同的show方法 一、多态的定义 同类型的对象&#xff0c;表现出的不同形态&#xff08;对象的多种形态&#xff09; 二…...

2023-09-10 LeetCode每日一题(课程表 II)

2023-09-10每日一题 一、题目编号 210. 课程表 II二、题目链接 点击跳转到题目位置 三、题目描述 现在你总共有 numCourses 门课需要选&#xff0c;记为 0 到 numCourses - 1。给你一个数组 prerequisites &#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示在…...

合并区间【贪心算法】

合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 class Solution {public int[][] merge(int[…...

2023,软件测试人的未来在哪里?

2023年&#xff0c;IT行业出现空前的萧条&#xff0c;首先是年初一开始各大厂像着了魔似的不约而同的纷纷裁员、降薪、奖金包缩水&#xff0c;随之而来的是需求萎缩&#xff0c;HC减少或封锁等等。 而有幸未被列入裁员名单的在职人员&#xff0c;庆幸之余也心有余悸&#xff0…...

Python中的Numpy向量计算(R与Python系列第三篇)

目录 一、什么是Numpy? 二、如何导入NumPy? 三、生成NumPy数组 3.1利用序列生成 3.2使用特定函数生成NumPy数组 &#xff08;1&#xff09;使用np.arange() &#xff08;2&#xff09;使用np.linspace() 四、NumPy数组的其他常用函数 &#xff08;1&#xff09;np.z…...

LeetCode刷题笔记【27】:贪心算法专题-5(无重叠区间、划分字母区间、合并区间)

文章目录 前置知识435. 无重叠区间题目描述参考<452. 用最少数量的箭引爆气球>, 间接求解直接求"重叠区间数量" 763.划分字母区间题目描述贪心 - 建立"最后一个当前字母"数组优化marker创建的过程 56. 合并区间题目描述解题思路代码① 如果有重合就合…...

nvidia-smi 命令详解

nvidia-smi 命令详解 1. nvidia-smi 面板解析2. 显存与GPU的区别 Reference: nvidia-smi命令详解 相关文章&#xff1a; nvidia-smi nvcc -V 及 CUDA、cuDNN 安装 nvidia-smi(NVIDIA System Management Interface) 是一种命令行实用程序&#xff0c;用于监控和管理 NVIDIA G…...

fork()函数的返回值

在程序中&#xff0c;int pd fork() 是一个典型的 fork() 调用。fork() 函数会创建一个新的进程&#xff0c;然后在父进程中返回子进程的进程ID&#xff08;PID&#xff09;&#xff0c;在子进程中返回0。所以 pd 的值会根据当前进程是父进程还是子进程而有所不同&#xff1a;…...

Stable Diffusion WebUI挂VPN不能跑图解决办法(Windows)

如何解决SD在打开VPN的状态不能运行的问题 在我们开VPN的时候会出现无法生成图片&#xff0c;也无法做其他任何事&#xff0c;这个时候是不是很着急呢&#xff1f; 别急&#xff0c;我这里会说明如何解决。 就像这样&#xff0c;运行半天生成不了图&#xff0c;有时还会出现…...

Android的本地数据

何为本地&#xff0c;即写完之后除非手动修改&#xff0c;否像嘎了一样在那固定死了 有些需求可能也会要求我们去写死数据&#xff0c;因为这需求是一成不变的&#xff0c;那么你通常会用什么方法写死呢&#xff1f; 1. 本地存储-SharedPreferences 此方法可以长时间保存于手…...

android NDK 开发包,网盘下载,不限速

记录下ndk 开发包的地址&#xff0c;分享给大家。 另外有Android studio的下载包&#xff0c; 在另一篇文章 链接&#xff1a;http://t.csdn.cn/JSr9x Android Studio.exe 下载 2023 最新更新&#xff0c;网盘下载_hsj-obj的博客-CSDN博客 主要是19-25&#xff0c;其他的没有…...

【每日一题Day320】LC2651计算列车到站时间 | 数学

计算列车到站时间【LC2651】](https://leetcode.cn/problems/calculate-delayed-arrival-time/) 给你一个正整数 arrivalTime 表示列车正点到站的时间&#xff08;单位&#xff1a;小时&#xff09;&#xff0c;另给你一个正整数 delayedTime 表示列车延误的小时数。 返回列车实…...

C语言柔性数组详解:让你的程序更灵活

柔性数组 一、前言二、柔性数组的用法三、柔性数组的内存分布四、柔性数组的优势五、总结 一、前言 仔细观察下面的代码&#xff0c;有没有看出哪里不对劲&#xff1f; struct S {int i;double d;char c;int arr[]; };还有另外一种写法&#xff1a; struct S {int i;double …...

Redis-带你深入学习数据类型list

目录 1、list列表 2、list相关命令 2.1、添加相关命令&#xff1a;rpush、lpush、linsert 2.2、查找相关命令&#xff1a;lrange、lindex、llen 2.3、删除相关命令&#xff1a;lpop、rpop、lrem、ltrim 2.4、修改相关命令&#xff1a;lset 2.5、阻塞相关命令&#xff1a…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...