SpringBoot / Vue 对SSE的基本使用(简单上手)

news2024/11/18 3:37:05

一、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 {

    @Autowired
    private 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等基础信息

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/984680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

异步编程 - 06 基于JDK中的Future实现异步编程(中)_CompletableFuture源码解析

文章目录 CompletableFuture 类图结构CompletionStage接口属性resultstackasyncPool 方法CompletableFuture<Void>runAsync(Runnable runnable)CompletableFuture<U> supplyAsync(Supplier<U>supplier)CompletableFuture<U> supplyAsync(Supplier<U…

【java】【SSM框架系列】【一】Spring

目录 一、简介 1.1 为什么学 1.2 学什么 1.3 怎么学 1.4 初识Spring 1.5 Spring发展史 1.6 Spring Framework系统架构图 1.7 Spring Framework学习线路 二、核心概念&#xff08;IoC/DI&#xff0c;IoC容器&#xff0c;Bean&#xff09; 2.1 概念 2.2 IoC入门案例 …

TVC广告片制作成本多少

电视是广告传播的主要媒介之一&#xff0c;具有广泛的受众群体和较高的覆盖率。通过在电视上播放广告片&#xff0c;企业可以将产品或者服务的信息传达给大量潜在客户&#xff0c;提高知名度和曝光度。接下来由深圳TVC广告片制作公司老友记小编从以下几个方面浅析制作一条TVC广…

SAP PI 配置SSL链接接口报错问题处理Peer certificate rejected by ChainVerifier

出现这种情况一般无非是没有正确导入证书或者证书过期的情况 第一种&#xff0c;如果没有导入证书的话&#xff0c;需要在NWA中的证书与验证-》CAs中导入管理员提供的证书&#xff0c;这里需要注意的是&#xff0c;需要导入完整的证书链。 第二种如果是证书过期的&#xff0c…

Java + Selenium + Appium自动化测试

一、启动测试机或者Android模拟器&#xff08;Genymotion俗称世界上最快的模拟器&#xff0c;可自行百度安装&#xff09; 二、启动Appium&#xff08;Appium环境安装可自行百度&#xff09; 三、安装应用到Genymotion上&#xff0c;如下图我安装一个计算机的小应用&#xff…

vit模型

AN IMAGE IS WORTH 16X16 WORDS:TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE 1、问题2、模型结构 1、问题 在视觉方面&#xff0c;注意力要么与卷积网络结合使用&#xff0c;要么用于替代卷积网络的某些组件&#xff0c;同时保持其整体结构不变。 我们证明了这种对CNNs的依赖…

【C#】关于Array.Copy 和 GC

关于Array.Copy 和 GC //一个简单的 数组copy 什么情况下会触发GC呢[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]public static void Copy(Array sourceArray,long sourceIndex,Array destinationArray,long destinationIndex,long length);当源和目…

微服务·架构组件之网关- Spring Cloud Gateway

微服务架构组件之网关- Spring Cloud Gateway 引言 微服务架构已成为构建现代化应用程序的关键范式之一&#xff0c;它将应用程序拆分成多个小型、可独立部署的服务。Spring Cloud Gateway是Spring Cloud生态系统中的一个关键组件&#xff0c;用于构建和管理微服务架构中的网…

芯片方案应用于终端产品时需要哪些技术支持和保障?

在芯片方案应用于终端产品时&#xff0c;客户可能会遇到三大类问题&#xff1a;一是芯片本身的质量缺陷&#xff1b;二是芯片与终端系统软硬件联合调试及验证&#xff1b;三是终端生产。 接下来&#xff0c;小编简短介绍启英泰伦是如何全方位支持客户项目&#xff0c;保障客户…

mac m1 代码调用 Stable Diffusion

from diffusers import DiffusionPipeline import torchpipe DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5") pipe pipe.to("mps") pipe.enable_attention_slicing()prompt "便利店开业" _ pipe(prompt, num_inferen…

分享一个基于SpringBoot+Vue的房屋在线装修预约系统源码

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

python读取.txt文件中某些关键字后面的内容 并根据该数据画图

感谢一下悦姐帮忙 import re#先把文件读进来&#xff0c;用read读入的是字符串&#xff0c;readlines是list with open(resok.txt) as f:txt f.read()dataset r5low:.*|5mix:.*|5normal:.* para rMAE: (.{6})#意思是MAE&#xff1a; 后面的六个东西 row_data re.findall(d…

6个免费图片素材库,高清无水印、无版权

推荐6个免费高清图片素材库&#xff0c;商用也可以&#xff0c;无需担心版权问题&#xff0c;收藏走一波~ 1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx 网站主要为新手设计师提供免费素材&#xff0c;这些素材的质量都很高&#xff0c;类别也很多&#xff0c;…

每日一练 | 网络工程师软考真题Day31

阅读以下说明&#xff0c;答复以下【问题1】至【问题7】 【说明】 某网络拓扑结构如图3-1所示。网络A中的DNS_Server1和网络B中的DNS_Server2分别安装有Windows Server 2003并启用了DNS效劳。DNS_Server1中安装有IIS6.0&#xff0c;建立了一个域名为 abc 的Web站点。 图3-1 【…

pytorch再次学习

目录 数据可视化切换设备device定义类打印每层的参数大小自动微分计算梯度禁用梯度追踪优化模型参数 模型保存模型加载 数据可视化 import torch from torch.utils.data import Dataset from torchvision import datasets from torchvision.transforms import ToTensor import…

Nginx中实现自签名SSL证书生成与配置

文章目录 一.相关介绍1.生成步骤2.相关名词介绍 二.Nginx中实现自签名SSL证书生成与配置1.私钥生成2.公钥生成3.生成解密的私钥key4.签名生成证书5.配置证书并验证6.登录 一.相关介绍 1.生成步骤 &#xff08;1&#xff09;生成私钥&#xff08;Private Key&#xff09;&…

elementUI——el-table自带排序使用问题

问题 排序表格默认第一列按降序排&#xff08;状态1&#xff09;&#xff0c;当点击其他列后&#xff08;状态2&#xff09;&#xff0c;改变日期&#xff0c;触发表格数据更新&#xff0c;发现列的排序还点亮在之前的操作上&#xff0c;没有按照默认来&#xff08;回到状态1&a…

运筹系列85:求解大规模tsp问题的julia代码

1. 大规模tsp问题的挑战 数学模型和精确解法见《运筹系列65&#xff1a;TSP问题的精确求解法概述》和《运筹系列80:使用Julia精确求解tsp问题》&#xff1a; variable(m, x[1:n,1:n], Bin,Symmetric) # 0-1约束 objective(model, Min, sum(x.*distmat)/2) constraint(model, …

Linux——线程详解(一)

索引 初识线程1.inux下的线程2.再谈进程3.理解页表4. 再次理解虚拟到物理的转化 线程的控制1.线程的创建2.线程异常3.验证pthread_join 的第二个参数4.线程的退出方式5. 线程的公有和私有6.pthread_t 与线程独立栈7.线程的局部性存储8.线程分离 初识线程 1.inux下的线程 之前了…

通过RTSP协议接入RTSP流媒体服务器EasyNVR视频监控汇聚平台的设备显示离线是什么原因?

EasyNVR安防视频云服务是基于RTSP/Onvif协议接入的视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端的分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等。平台丰富灵活的视频能力&#xff0c;可应用在智慧校园、智慧工厂、智慧水利等…