当前位置: 首页 > 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…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...