重学SpringBoot3-Spring WebFlux之SSE服务器发送事件

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》
期待您的点赞??收藏评论

在这里插入图片描述

Spring WebFlux之SSE服务器发送事件
  • 1. 什么是 SSE?
  • 2. Spring Boot 3 响应式编程与 SSE
    • 为什么选择响应式编程实现 SSE?
  • 3. 实现 SSE 的基本步骤
    • 3.1 创建 Spring Boot 项目
    • 3.2 实现服务端推送 SSE 事件流
    • 3.3 客户端接收 SSE 数据
  • 4. 测试 SSE
  • 5. 优化与扩展
    • 5.1 增加随机数据推送
    • 5.2 增加心跳检测(Ping)
    • 5.3 使用 `MediaType.TEXT_EVENT_STREAM` 响应
  • 6. SSE 与 WebSocket 的对比
  • 7. 总结

ChatGPT 刚出的时候,让大伙很好奇的是它是如何实现的逐字输出的?答案就是 SSE (服务器发送事件)。随着实时数据和响应式编程的需求不断增加,服务器发送事件(Server-Sent Events,简称 SSE)在现代 Web 应用程序中越来越受欢迎。SSE 提供了一种轻量级的服务器推送数据给客户端的方式,适合用于监控、实时通知、股票价格更新等场景。

在 Spring Boot 3 中,结合响应式编程的理念,SSE 的实现变得更加简洁和高效。本文将详细介绍如何使用 Spring Boot 3 来实现 SSE 服务端推送,并讨论响应式编程在此过程中的重要性和优势。

1. 什么是 SSE?

服务器发送事件(SSE) 是一种从服务器向客户端推送数据的技术,属于 HTML5 的一部分。与传统的 HTTP 请求-响应模型不同,SSE 是单向的,服务器可以持续不断地向客户端发送数据,而客户端通过一次长连接持续接收这些更新。

相比 WebSocket,SSE 有以下特点:

  • 单向通信:SSE 仅允许服务器向客户端推送数据,客户端无法向服务器发送数据。
  • 基于 HTTP 协议:SSE 是建立在 HTTP 协议之上的,浏览器原生支持,不需要额外的协议处理。
  • 自动重连:SSE 支持自动重连,当连接意外断开时,客户端会自动尝试重新连接服务器。

2. Spring Boot 3 响应式编程与 SSE

Spring Boot 3 提供了对响应式编程的全面支持,基于 Project Reactor 实现异步、非阻塞的流式数据处理。而响应式编程非常适合实现 SSE,因为它允许我们以非阻塞的方式持续推送数据,而不会阻塞服务器的资源。

Spring WebFlux 是 Spring Boot 3 中用于构建响应式应用的核心框架,它可以无缝集成 SSE,为我们提供简单高效的服务器推送功能。

为什么选择响应式编程实现 SSE?

传统的阻塞式编程在处理长连接(如 SSE)时可能会占用大量服务器资源。响应式编程通过非阻塞 I/O 操作,不仅可以高效处理长时间的连接,还能在有新数据时立即推送给客户端。响应式流(如 Flux)天然适合于这种流式数据推送场景。

3. 实现 SSE 的基本步骤

我们将通过以下步骤实现一个简单的 SSE 服务端推送应用:

  1. 创建 Spring Boot 项目并引入 WebFlux 依赖。
  2. 实现服务端推送 SSE 事件流。
  3. 编写客户端接收 SSE 数据。
  4. 测试与优化。

3.1 创建 Spring Boot 项目

首先,创建一个新的 Spring Boot 3 项目,并确保引入了 spring-boot-starter-webflux 依赖。可以通过 Maven 或 Gradle 配置:

Maven 依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
</dependencies>

3.2 实现服务端推送 SSE 事件流

在 Spring WebFlux 中,SSE 通过返回 Flux<ServerSentEvent<T>> 这种响应流来实现。下面我们实现一个简单的 SSE 控制器,它会每隔一段时间向客户端推送当前的时间信息。

示例控制器

package com.coderjia.boot3webflux.controller;

import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import reactor.core.publisher.Flux;

import java.time.Duration;
import java.time.LocalTime;

/**
 * @author CoderJia
 * @create 2024/10/27 下午 07:03
 * @Description
 **/
@Controller
public class SseController {

    @GetMapping("/sse/stream")
    public Flux<ServerSentEvent<String>> streamSse() {
        return Flux.interval(Duration.ofSeconds(1))
                .map(sequence -> ServerSentEvent.<String>builder()
                        .id(String.valueOf(sequence))
                        .event("periodic-event")
                        .data("Current time: " + LocalTime.now())
                        .build());
    }
}

解释

  • Flux.interval(Duration.ofSeconds(1)):创建一个每秒发出事件的响应式流。
  • ServerSentEvent.builder():构建 ServerSentEvent 对象,它可以包含 ideventdata 等信息,符合 SSE 规范。
  • map():将流中的每个事件映射为 ServerSentEvent,并附带当前的时间信息。

3.3 客户端接收 SSE 数据

客户端可以使用 JavaScript 原生的 EventSource API 来接收服务器发送的 SSE 数据流。

示例 HTML + JavaScript 客户端

resources/static/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE Example</title>
</head>
<body>
<h1>Server-Sent Events (SSE) Example</h1>
<div id="messages"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>

    const http = axios.create({
        baseURL: 'http://localhost:8080/',
        timeout: 100000,
        responseType: 'stream',
        onDownloadProgress: function (progressEvent) {
            // 获取 messages 元素
            const messagesElement = document.getElementById("messages");

            // 清除现有内容
            messagesElement.innerHTML = "";

            // 添加新内容
            const newElement = document.createElement("div");
            newElement.innerHTML = progressEvent.event.currentTarget.responseText + "<br/>";
            messagesElement.appendChild(newElement);
        },
    });

    http.get('/sse/stream')
        .then(function (response) {
            // 处理成功情况
            console.log(response);
        })
        .catch(function (error) {
            // 处理错误情况
            console.log(error);
        })
        .finally(function () {
            // 总是会执行
        });
</script>
</body>
</html>

解释

  • EventSource("/sse/stream"):EventSource 是浏览器提供的一个用于和服务器建立连接,接收服务器发送事件的接口。在客户端发起与服务器的 SSE 长连接。服务器通过 /sse/stream 推送事件。
  • onmessage:处理服务器发送的消息,并将消息显示在页面上。
  • onerror:当连接发生错误时关闭连接,避免持续消耗资源。

4. 测试 SSE

运行 Spring Boot 应用,并访问 /sse/stream,可以看到服务器每秒钟向客户端推送一次当前时间信息。

接口请求

header 里的 Content-Type 为 text/event-stream

Content-Type

可以通过浏览器打开 http://localhost:8080/,在页面中将会每秒钟显示一次服务器推送的数据流。这就验证了 SSE 在 Spring Boot 3 中的实现。

浏览器展示

5. 优化与扩展

5.1 增加随机数据推送

为了模拟更真实的场景,可以增加一些随机数据或实时数据更新。假设我们希望推送随机的股票价格,我们可以这样修改:

@GetMapping("/sse/stocks")
public Flux<ServerSentEvent<String>> streamStockPrices() {
    return Flux.interval(Duration.ofSeconds(1))
            .map(sequence -> ServerSentEvent.<String>builder()
                    .id(String.valueOf(sequence))
                    .event("stock-update")
                    .data("Stock price: $" + ThreadLocalRandom.current().nextInt(100, 200))
                    .build());
}

在这个例子中,每秒推送一次随机的股票价格更新。

5.2 增加心跳检测(Ping)

SSE 连接如果长时间没有数据传输,可能会被中断。为此,SSE 规范推荐发送 “ping” 消息来保持连接活跃。可以通过 ServerSentEventcomment() 来发送心跳信息:

@GetMapping("/sse/stream-with-ping")
public Flux<ServerSentEvent<String>> streamWithPing() {
    return Flux.interval(Duration.ofSeconds(1))
            .map(sequence -> {
                if (sequence % 5 == 0) {  // 每5秒发送一次心跳
                    return ServerSentEvent.<String>builder()
                            .comment("ping")
                            .build();
                } else {
                    return ServerSentEvent.<String>builder()
                            .data("Current time: " + LocalTime.now())
                            .build();
                }
            });
}

5.3 使用 MediaType.TEXT_EVENT_STREAM 响应

虽然 ServerSentEvent 是处理 SSE 的标准类,但你也可以直接返回 Flux<T>,Spring 会自动将其转换为事件流。如果你想简化代码,可以这样写:

@GetMapping(value = "/sse/simple", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> simpleSse() {
    return Flux.interval(Duration.ofSeconds(1))
            .map(sequence -> "Current time: " + LocalTime.now());
}

这里直接返回 Flux<String>,Spring WebFlux 会自动推送数据。

6. SSE 与 WebSocket 的对比

SSE 和 WebSocket 都是实时通信的重要技术,但它们有不同的适用场景:

  • SSE:单向通信,服务器推送数据到客户端,适合轻量级的通知、监控、消息更新等场景。使用简单,基于 HTTP。
  • WebSocket:双向通信,适合复杂的交互场景,如实时聊天、在线游戏等。WebSocket 是基于 TCP 的全双工连接,相对更复杂。

对于简单的实时更新场景,如股票价格更新、推送通知等,SSE 更加轻量且易于实现。

7. 总结

Spring Boot 3 提供了简单、强大的 SSE 实现,结合响应式编程的特性,使得我们可以轻松构建高效的服务器推送应用。在实际项目中,SSE 非常适合用于推送实时数据或监控信息,尤其在需要轻量且可靠的单向通信时。通过 Spring WebFlux 和 Project Reactor,SSE 的实现可以以非阻塞的方式运行,极大提升了应用的并发处理能力。

希望这篇博客对你理解 Spring Boot 3 中的 SSE 服务端推送有所帮助,如果有任何问题或想法,欢迎讨论!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyNext当自定义Dialog有TextInput输入框组件时,弹出软键盘时,dialog布局与软键盘之间有16vp间隙,如何解决,正宗方案

网上的解决方案都是在Dialog组件的根容器中设置偏移量.offset({x:0,y:16}) 大概这种的&#xff0c;这种垃圾解决方式最不可靠&#xff0c;倘若dialog输入框时根据状态变量动态显示的话&#xff0c;即使设置了也没有用 正宗解决方案 首先自定义dialog 三个地方需要注意 1、cu…

idea 如何使用deepseek 保姆级教程

1.安装idea插件codegpt 2.注册deepseek并生成apikey deepseek 开发平台&#xff1a; DeepSeek​​​​​​​ 3.在idea进行codegpt配置 打开idea的File->Settings->Tools->CodeGPT->Providers->Custom OpenAI Chat Completions的URL填写 https://api.deepseek…

响应式编程库(三) -r2dbc

r2dbc整合 什么是r2dbc版本选择简单试用整合springbootDatabaseClient 进行查询使用Repository接口(对应mapper)实体类复杂查询&#xff08;一对一&#xff09;实体类转换器测试代码一对多关系 什么是r2dbc 反应式关系数据库连接&#xff08;R2DBC&#xff09;项目为关系数据库…

第26场蓝桥入门赛

5.扑克较量【算法赛】 - 蓝桥云课 C&#xff1a; #include <iostream> #include <algorithm> using namespace std;int a[100005];int main() {int n,k;cin>>n>>k;for (int i1; i<n; i)cin>>a[i], a[i] % k;sort(a1, a1n);int mx a[1]k-a…

封装descriptions组件,描述,灵活

效果 1、组件1&#xff0c;dade-descriptions.vue <template><table><tbody><slot></slot></tbody> </table> </template><script> </script><style scoped>table {width: 100%;border-collapse: coll…

【专题】2025年我国机器人产业发展形势展望:人形机器人量产及商业化关键挑战报告汇总PDF洞察(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p39668 机器人已广泛融入我们生活的方方面面。在工业领域&#xff0c;它们宛如不知疲倦的工匠&#xff0c;精准地完成打磨、焊接等精细工作&#xff0c;极大提升了生产效率和产品质量&#xff1b;在日常生活里&#xff0c;它们是贴心…

能够从优秀工控app中学到什么?

美的工控类APP设计在多个方面都有值得学习的地方&#xff0c;包括用户体验、视觉设计、功能布局等&#xff0c;以下是具体内容&#xff1a; 用户体验方面 精准的用户需求洞察&#xff1a;工控类APP的目标用户通常是专业的工程师、技术人员等&#xff0c;其设计会深入了解这些…

334递增的三元子序列贪心算法(思路解析+源码)

文章目录 题目思路解析源码总结题目 思路解析 有两种解法:解法一:动态规划(利用dp找到数组最长递增序列长度,判断是否大于3即可)本题不适用,因为时间复杂度为O(n^2),超时。 解法二:贪心算法:解法如上图,题目要求长度为三,设置第一个元素为长度1的值,是指长度二的…

深入探究 C++17 std::is_invocable

文章目录 一、引言二、std::is_invocable 概述代码示例输出结果 三、std::is_invocable 的工作原理简化实现示例 四、std::is_invocable 的相关变体1. std::is_invocable_r2. std::is_nothrow_invocable 和 std::is_nothrow_invocable_r 五、使用场景1. 模板元编程2. 泛型算法 …

P1049 装箱问题(dp)

#include<bits/stdc.h> using namespace std;int main() {int v,n;cin>>v>>n;int a[30];int dp[20005];for(int i0;i<n;i){cin>>a[i];}memset(dp,0,sizeof(dp));// 设置所有元素为0&#xff0c;表示最大体积为0for(int i0;i<n;i){for(int jv;j&…

Groovy基础

引言&#xff1a; Groovy 是一种基于 Java 平台的动态编程语言&#xff08;指在运行时进行类型检查的语言。在使用动态语言编写程序时&#xff0c;变量的类型不需要在声明时明确指定&#xff0c;而是在运行时根据赋给变量的值来确定类型。动态语言在代码执行过程中会进行类型检…

Flink CDC YAML:面向数据集成的 API 设计

摘要&#xff1a;本文整理自阿里云智能集团 、Flink PMC Member & Committer 徐榜江&#xff08;雪尽&#xff09;老师在 Flink Forward Asia 2024 数据集成&#xff08;一&#xff09;专场中的分享。主要分为以下四个方面&#xff1a; Flink CDC YAML API Transform A…

OpenCV:视频背景减除

目录 简述 1. MOG &#x1f537;1.1 主要特点 &#x1f537;1.2 代码示例 &#x1f537;1.3 运行效果 2. MOG2 &#x1f537;2.1 主要特点 &#x1f537;2.2 代码示例 &#x1f537;2.3 运行效果 3. KNN 4. GMG 5. CNT 6. LSBP 7. 如何选择适合的接口&#xff…

PAT乙级( 1009 说反话 1010 一元多项式求导)C语言版本超详细解析

1009 说反话 给定一句英语&#xff0c;要求你编写程序&#xff0c;将句中所有单词的顺序颠倒输出。 输入格式&#xff1a; 测试输入包含一个测试用例&#xff0c;在一行内给出总长度不超过 80的字符串。字符串由若干单词和若干空格组成&#xff0c;其中单词是由英文字母&#x…

OpenCV2D 特征框架 (19)目标检测类cv::CascadeClassifier的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::CascadeClassifier 是 OpenCV 中用于对象检测的一个核心类&#xff0c;特别适用于基于 Haar 特征和 LBP&#xff08;局部二进制模式&#xf…

大数据学习之SparkSql

95.SPARKSQL_简介 网址&#xff1a; https://spark.apache.org/sql/ Spark SQL 是 Spark 的一个模块&#xff0c;用于处理 结构化的数据 。 SparkSQL 特点 1 易整合 无缝的整合了 SQL 查询和 Spark 编程&#xff0c;随时用 SQL 或 DataFrame API 处理结构化数据。并且支…

RabbitMQ 从入门到精通:从工作模式到集群部署实战(四)

#作者&#xff1a;闫乾苓 系列前几篇&#xff1a; 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;一&#xff09;》&#xff1a;link 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;二&#xff09;》&#xff1a; lin…

ip地址是手机号地址还是手机地址

在数字化生活的浪潮中&#xff0c;IP地址、手机号和手机地址这三个概念如影随形&#xff0c;它们各自承载着网络世界的独特功能&#xff0c;却又因名称和功能的相似性而时常被混淆。尤其是“IP地址”这一术语&#xff0c;经常被错误地与手机号地址或手机地址划上等号。本文旨在…

Django开发入门 – 0.Django基本介绍

Django开发入门 – 0.Django基本介绍 A Brief Introduction to django By JacksonML 1. Django简介 1) 什么是Django? 依据其官网的一段解释&#xff1a; Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. …

深度解析DeepSeek模型系列:从轻量级到超大规模(附DeepSeek硬件配置清单)

在人工智能领域&#xff0c;深度学习模型的选择对于任务的执行效率和精度至关重要。DeepSeek模型系列提供了多种不同参数量的版本&#xff0c;以满足不同场景下的需求。本文将详细解析DeepSeek模型系列的特点、适用场景以及硬件需求。 DeepSeek模型系列概览 DeepSeek模型系列…