SSE(Server Sent Event)实战(2)- Spring MVC 实现

一、服务端实现

  1. 使用 @RestController 注解创建一个控制器类(Controller)

  2. 创建一个方法来创建一个客户端连接,它返回一个 SseEmitter,处理 GET 请求并产生(produces)文本/事件流 (text/event-stream)

  3. 创建一个新的 SseEmitter, 保存它并从方法中返回

  4. 在另一个线程中异步发送事件, 先拿到保存的 SseEmitter 并根据需要多次调用调用SseEmitter.send()方法

  5. 完成事件发送, 调用 SseEmitter.complete() 方法

  6. 要异常完成发送事件,请调用 SseEmitter.completeWithError() 方法

/** xxx.com* Copyright (C) 2021-2024 All Rights Reserved.*/
package com.sse.demo.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author xxx* @version SseController.java, v 0.1 2024-07-11 10:11*/
@Slf4j
@RestController
@RequestMapping("/sse")
public class SseController {private static final Map<String, SseEmitter> SSE_EMITTER_MAP = new ConcurrentHashMap<>();/*** 创建连接*/@GetMapping("/create-connect")public SseEmitter createConnect(@RequestParam("userId") String userId) {try {// 设置超时时间,0表示不过期。默认30秒SseEmitter sseEmitter = new SseEmitter(0L);// 注册回调sseEmitter.onCompletion(() -> removeSseConnection(userId, "SSE连接已关闭"));sseEmitter.onError(throwable -> removeSseConnection(userId, "SSE连接出现错误"));sseEmitter.onTimeout(() -> removeSseConnection(userId, "SSE连接超时"));SSE_EMITTER_MAP.put(userId, sseEmitter);log.info("创建了用户[{}]的SSE连接", userId);return sseEmitter;} catch (Exception e) {log.error("创建新的SSE连接异常,当前用户:" + userId, e);return null;}}/*** 发送消息*/@GetMapping("/send-message")public void sendMessage(@RequestParam("userId") String userId, @RequestParam("message") String message) {SseEmitter sseEmitter = SSE_EMITTER_MAP.get(userId);if (sseEmitter != null) {try {sseEmitter.send(SseEmitter.event().name("message").data(message).reconnectTime(5000));log.info("给用户[{}]发送消息成功: {}", userId, message);} catch (Exception e) {log.error("给用户[{}]发送消息失败: {}", userId, e.getMessage(), e);// 如果发送失败,尝试从map中移除失效的SseEmitterremoveSseConnection(userId, "发送消息失败");}} else {log.info("用户[{}]的SSE连接不存在或已关闭,无法发送消息", userId);}}private void removeSseConnection(String userId, String reason) {SSE_EMITTER_MAP.computeIfPresent(userId, (key, sseEmitter) -> {sseEmitter.complete();log.info("用户[{}]的SSE连接已移除,原因:{}", userId, reason);return null;});}
} 

二、客户端实现

创建多个 index.html文件,放在 static 目录下,用不同的浏览器打开,实现向多个用户推送的场景。
在这里插入图片描述


<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>SSE Demo</title><script>        document.addEventListener('DOMContentLoaded', function () {var userId = "1";// 创建一个新的EventSource对象var source = new EventSource('http://localhost:8080/sse/create-connect?userId=' + userId);// 当连接打开时触发source.onopen = function (event) {console.log('SSE连接已打开');};// 当从服务器接收到消息时触发source.onmessage = function (event) {// event.data 包含服务器发送的文本数据console.log('接收到消息:', event.data);// 在页面上显示消息var messagesDiv = document.getElementById('messages');if (messagesDiv) {messagesDiv.innerHTML += '<p>' + event.data + '</p>'; // 直接使用event.data} else {console.error('未找到消息容器元素');}};// 当发生错误时触发source.onerror = function (event) {console.error('SSE连接错误:', event);};});</script>
</head>
<body>
<div id="messages"><!-- 这里将显示接收到的消息 -->
</div>
</body>
</html>

三、启动项目

  1. 运行 Spring 项目
    在这里插入图片描述
  2. 浏览器打开 index.html文件
    在这里插入图片描述
  3. 调用发送消息接口
    curl http://localhost:8080/sse/send-message\?userId\=1\&message\=test0001
    在这里插入图片描述

打开多个连接,用 userId 就可以实现向不同的用户推送的逻辑了。

四、总结

上面已经实现了最基本的消息推送需求,但是我们还可以思考一下实际生产中,我们还需要做哪些优化?

  1. 如果我们服务设置了最大连接时间,比如 3 分钟,而服务端又长时间没有消息推送给客户端,导致长连接被关闭该怎么办?
  2. 实际生产环境,我们肯定是多个实例部署,那么怎么保证创建连接和发送消息是在同一个实例完成?如果不是一个实例,就意味着用户没有建立连接,消息肯定发送失败。

下一篇博客,再做具体优化。

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

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

相关文章

如何使用Milvus Cloud进行稀疏向量搜索

如何使用Milvus Cloud进行向量搜索Milvus Cloud 是一款高度可扩展、性能出色的开源向量数据库。在最新的 2.4 版本中,Milvus Cloud 支持了稀疏和稠密向量(公测中)。本文将利用 Milvus Cloud 2.4 来存储数据集并执行向量搜索。 接下来,我们将演示如何利用 Milvus Cloud 在 M…

[GXYCTF2019]Ping Ping Ping1

打开靶机 结合题目名称&#xff0c;考虑是命令注入&#xff0c;试试ls 结果应该就在flag.php。尝试构造命令注入载荷。 cat flag.php 可以看到过滤了空格,用 $IFS$1替换空格 还过滤了flag&#xff0c;我们用字符拼接的方式看能否绕过,ag;cat$IFS$1fla$a.php。注意这里用分号间隔…

睡前故事—绿色科技的未来:可持续发展的梦幻故事

欢迎来到《Bedtime Stories Time》。这是一个我们倾听、放松、并逐渐入睡的播客。感谢你收听并支持我们&#xff0c;希望你能将这个播客作为你睡前例行活动的一部分。今晚我们将讲述绿色科技的未来&#xff1a;可持续发展的梦幻故事的故事。一个宁静的夜晚&#xff0c;希望你现…

0602STM32定时器输出比较

STM32定时器输出比较 PWM简介 主要用来输出PWM波形&#xff0c;PWM波形又是驱动电机的必要条件&#xff0c;所以如果想用STM32做一些有电机的项目&#xff0c;比如智能车&#xff0c;机器人等。那输出比较功能就要认真掌握 1.PWM驱动LED呼吸灯 2.PWM驱动舵机 3.PWM驱动直流电机…

搜维尔科技:【研究】触觉技术将在5年内以8种方式改变人们的世界

触觉技术在过去几年中发展迅猛&#xff0c;大大提高了反馈的精确度和真实度。其应用产生了真正的影响&#xff0c;数百家公司和企业都集成了触觉技术来增强培训和研究模拟。 虽然触觉技术主要用于 B2B 层面&#xff0c;但触觉技术可能会彻底改变我们的生活&#xff0c;尤其是通…

视频共享融合赋能平台LntonCVS视频监控业务平台技术方案详细介绍

LntonCVS国标视频综合管理平台是一款智慧物联应用平台&#xff0c;核心技术基于视频流媒体&#xff0c;采用分布式和负载均衡技术开发&#xff0c;提供广泛兼容、安全可靠、开放共享的视频综合服务。该平台功能丰富&#xff0c;包括视频直播、录像、回放、检索、云存储、告警上…

【数据结构】详解堆

一、堆的概念 堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵 完全二叉树的 数组对象。 堆是非线性数据结构&#xff0c;相当于一维数组&#xff0c;有两个直接后继。 如果有一个关键码的集合K { k₀&#xff0c;k₁&#xff0c;k₂ &#xff0…

数据结构(双向链表)

链表的分类 链表的结构⾮常多样&#xff0c;以下情况组合起来就有8种&#xff08;2 x 2 x 2&#xff09;链表结构&#xff1a; 虽然有这么多的链表的结构&#xff0c;但是我们实际中最常⽤还是两种结构&#xff1a;单链表和双向带头循环链表 1.⽆头单向⾮循环链表&#xff1a…

第十课:telnet(远程登入)

如何远程管理网络设备&#xff1f; 只要保证PC和路由器的ip是互通的&#xff0c;那么PC就可以远程管理路由器&#xff08;用telnet技术管理&#xff09;。 我们搭建一个下面这样的简单的拓扑图进行介绍 首先我们点击云&#xff0c;把云打开&#xff0c;点击增加 我们绑定vmn…

【面试题】Redo log和Undo log

Redo log 介绍Redo log之前我们需要了解一下&#xff0c;mysql数据操作的流程&#xff1a; 上述就是数据操作的流程图&#xff0c;可以发现sql语句并不是直接操作的磁盘而是通过操作内存&#xff0c;然后进行内存到磁盘的一个同步。这里我们必须要了解一些区域&#xff1a; 缓…

华为HCIP Datacom H12-821 卷42

42.填空题 如图所示&#xff0c;MSTP网络中SW1为总根&#xff0c;请将以下交换机与IST域根和主桥配对。 参考答案&#xff1a;主桥1468 既是IST域根又是主桥468 既不是又不是就是25 解析&#xff1a; 主桥1468 既是IST域根又是主桥468 既不是又不是就是25 43.填空题 网络有…

[日进斗金系列]用码上飞解决企微开发维修管理系统的需求

前言&#xff1a; 今天跟大家唠唠如何用小money生 大money的方法&#xff0c;首先我们需要准备一个工具。 这个工具叫码上飞CodeFlying&#xff0c;它是目前国内首发的L4级自动化智能软件开发平台。 它可以在短时间内&#xff0c;与AI进行几轮对话就能开发出一个可以解决实际…

WEB前端06-BOM对象

BOM浏览器对象模型 浏览器对象模型&#xff1a;将浏览器的各个组成部分封装成对象。是用于描述浏览器中对象与对象之间层次关系的模型&#xff0c;提供了独立于页面内容、并能够与浏览器窗口进行交互的对象结构。 组成部分 Window&#xff1a;浏览器窗口对象 Navigator&…

区块链资料

Quantstamp - Public Security Assessments smart-contract-sanctuary-bsc/contracts/mainnet at master tintinweb/smart-contract-sanctuary-bsc GitHub https://github.com/slowmist/Cryptocurrency-Security-Audit-Guide/blob/main/README_CN.md sFuzz: 高效自适应的智…

Spring3(代理模式 Spring1案例补充 Aop 面试题)

目录 一、代理模式 介绍 意图 主要解决的问题 使用场景 实现方式 关键代码 应用实例 优点 缺点 使用建议 注意事项 结构 什么是代理模式&#xff1f; 为什么要用代理模式&#xff1f; 有哪几种代理模式&#xff1f; 1. 静态代理 实现 2. 基于接口的动态代理…

《昇思25天学习打卡营第20天|GAN图像生成》

生成对抗网络&#xff08;GAN&#xff09;是一种深度学习模型&#xff0c;用于生成逼真的图像。在手写数字识别的任务中&#xff0c;GAN 可以用来生成与真实手写数字相似的图像&#xff0c;以增强模型的训练数据集。GAN 主要由两个部分组成&#xff1a;生成器&#xff08;Gener…

Spring Data Jpa 原生SQL联表查询返回自定义DTO

Spring Data Jpa 原生SQL联表查询返回自定义DTO 方案一&#xff1a;返回Map 这个就不说了 方案二&#xff1a;实体定义成接口的形式 该方式最直观&#xff01;&#xff01;推荐&#xff01;&#xff01;&#xff01; 注意&#xff1a;XxxDto是interface接口&#xff0c;而…

WPF/C#:在WPF中如何实现依赖注入

前言 本文通过 WPF Gallery 这个项目学习依赖注入的相关概念与如何在WPF中进行依赖注入。 什么是依赖注入 依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff09;是一种设计模式&#xff0c;用于实现控制反转&#xff08;Inversion of Control&#xff0…

[Redis]典型应用——缓存

什么是缓存 缓存&#xff08;Cache&#xff09;是一种用于临时存储数据的机制&#xff0c;目的是提高数据访问速度和系统性能。 核心思路就是把一些常用的数据放到触手可及(访问速度更快)的地方&#xff0c;方便随时读取 缓存是一个相对的概念&#xff0c;比如说&#xff0c…

EE trade:强平和爆仓的区别

在金融交易市场中&#xff0c;杠杆交易的引入&#xff0c;让投资者可以用少量的资金撬动更大的头寸&#xff0c;获取更大的收益。然而&#xff0c;杠杆交易也带来了更大的风险&#xff0c;一旦市场波动&#xff0c;投资者可能会面临强平或爆仓的风险。了解强平和爆仓的区别&…