09. Springboot集成sse服务端推流

news2024/11/15 21:54:54

目录

1、前言

2、什么是SSE

2.1、技术原理

2.2、SSE和WebSocket

2.2.1、SSE (Server-Sent Events)

2.2.2、WebSocket

2.2.3、选择 SSE 还是 WebSocket?

3、Springboot快速集成

3.1、添加依赖

3.2、创建SSE控制器

3.2.1、SSEmitter创建实例

3.2.2、SSEmitter API

3.2.3、SSEmitter注册回调

4、小结


1、前言

如果项目中有一个场景,假设对接ChatGPT或对接天气类接口的时候,需要服务端主动往客户端进行消息推送或推流。通常的做法有:

  • 客户端提供接收数据接口,服务端开启定时轮询,定时向客户端发起http请求
  • 客户端提供定时轮询服务,定时向服务端发起http请求接口
  • 使用websocket实时通讯

那么今天再介绍另一种机制:SSE,也就是服务器发送事件机制。

2、什么是SSE

SSE(Server-Sent Events)是一种允许服务器向客户端推送实时数据的技术,它建立在 HTTP 和简单文本格式之上,提供了一种轻量级的服务器推送方式,通常也被称为“事件流”(Event Stream)。他通过在客户端和服务端之间建立一个长连接,并通过这条连接实现服务端和客户端的消息实时推送。

2.1、技术原理

SSE是建立在HTTP协议之上的,所以原理比较简单,也与HTTP原理类似:

1)建立连接:

客户端通过普通的 HTTP 请求向服务器发起连接请求,类似于普通的 Web 请求。这个请求的关键在于使用了 text/event-stream 的 MIME 类型,告知服务器该请求是 SSE 请求。

httpCopy codeGET /sse/stream HTTP/1.1
Host: example.com
Accept: text/event-stream

2)服务器处理请求:

服务器接收到 SSE 请求后,会在连接上保持打开状态,不会立即关闭。这是与普通的请求-响应模式的主要不同之处。服务器端通过这个持久连接向客户端发送数据。

3)数据推送:

服务器端通过打开的连接,周期性地向客户端发送消息。这些消息以文本的形式发送,并遵循一定的格式,通常以 data 字段表示消息内容。

httpCopy codeHTTP/1.1 200 OK
Content-Type: text/event-stream

data: This is a message\n\n

上述例子中,data 字段包含了实际的消息内容,两个换行符(\n\n)表示消息的结束。

4)客户端接收消息:

客户端通过监听连接的 message 事件来接收服务器推送的消息。一旦接收到消息,客户端可以采取相应的操作,例如更新界面内容。

javascriptCopy codeconst eventSource = new EventSource('/sse/stream');

eventSource.onmessage = function (event) {
    console.log('Received message:', event.data);
    // 处理消息,例如更新界面
};

5)连接关闭:

当服务器端不再需要向客户端推送消息时,或者发生错误时,服务器可以关闭连接。客户端也可以通过调用 eventSource.close() 来关闭连接。

2.2、SSE和WebSocket

提到SSE,那自然要提一下WebSocket了。WebSocket是一种HTML5提供的全双工通信协议(指可以在同一时间内允许两个设备之间进行双向发送和接收数据的通信协议),基于TCP协议,并复用HTTP的握手通道(允许一次TCP连接中传输多个HTTP请求和相应),常用于浏览器与服务器之间的实时通信。

SSE和WebSocket尽管功能类似,都是用来实现服务器向客户端实时推送数据的技术,但还是有一定区别:

2.2.1、SSE (Server-Sent Events)

  1. 简单性:SSE 使用简单的 HTTP 协议,通常建立在标准的 HTTP 或 HTTPS 连接之上。这使得它对于一些简单的实时通知场景非常适用,特别是对于服务器向客户端单向推送数据。
  2. 兼容性:SSE 在浏览器端具有较好的兼容性,因为它是基于标准的 HTTP 协议的。即使在一些不支持 WebSocket 的环境中,SSE 仍然可以被支持。
  3. 适用范围:SSE 适用于服务器向客户端单向推送通知,例如实时更新、事件通知等。但它仅支持从服务器到客户端的单向通信,客户端无法直接向服务器发送消息。

2.2.2、WebSocket

  1. 全双工通信: WebSocket 提供了全双工通信,允许客户端和服务器之间进行双向实时通信。这使得它适用于一些需要双向数据交换的应用,比如在线聊天、实时协作等。
  2. 低延迟:WebSocket 的通信开销相对较小,因为它使用单一的持久连接,而不像 SSE 需要不断地创建新的连接。这可以降低通信的延迟。
  3. 适用范围: WebSocket 适用于需要实时双向通信的应用,特别是对于那些需要低延迟、高频率消息交换的场景。

2.2.3、选择 SSE 还是 WebSocket?

  • 简单通知场景:如果你只需要服务器向客户端推送简单的通知、事件更新等,而不需要客户端与服务器进行双向通信,那么 SSE 是一个简单而有效的选择。
  • 双向通信场景:如果你的应用需要实现实时双向通信,例如在线聊天、协作编辑等,那么 WebSocket 是更合适的选择。
  • 兼容性考虑: 如果你的应用可能在一些不支持 WebSocket 的环境中运行,或者需要考虑到更广泛的浏览器兼容性,那么 SSE 可能是一个更可行的选择。

3、Springboot快速集成

3.1、添加依赖

Springboot项目中,sse不需要额外添加依赖,引用了web相关的springboot依赖即可:

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

3.2、创建SSE控制器

这里简单创建一个控制器类,用于处理SSE请求。在JAVA中通常使用SSEmitter来实现sse的消息推送。

package com.example.springbootsse.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Date;


@RestController
@RequestMapping("/sse")
public class SSEmitterController {

    @GetMapping("/stream")
    public SseEmitter stream() {
        // 用于创建一个 SSE 连接对象
        SseEmitter emitter = new SseEmitter();

        // 在后台线程中模拟实时数据
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    // emitter.send() 方法向客户端发送消息
                    // 使用SseEmitter.event()创建一个事件对象,设置事件名称和数据
                    emitter.send(SseEmitter.event().name("message").data("[" + new Date() + "] Data #" + i));
                    Thread.sleep(1000);
                }
                // 数据发送完成后,关闭连接
                emitter.complete(); 
            } catch (IOException | InterruptedException e) {
                 // 发生错误时,关闭连接并报错
                emitter.completeWithError(e);
            }
        }).start();
        return emitter;
    }
}

查看执行结果,可以看到每一秒服务端都会自动像客户端推送messag消息:

我们来关注下SSEmitter这个类,SseEmitter 是 Spring Framework 中用于实现 Server-Sent Events(SSE)的一个类。它允许服务器向客户端推送数据,通过建立一个持久连接,实现服务器向客户端的实时单向通信。在 Spring 框架中,SseEmitter 类通常用于处理 SSE 请求,推送事件给客户端。

3.2.1、SSEmitter创建实例

SSEmitter提供了两个构造函数用于创建实例。在创建实例时,我们可以指定超时时间timeout,如果传0或使用无参构造,则表示永不过期。连接超时是指在一段时间内没有数据传输时,连接将被认为是超时的,并自动关闭。

3.2.2、SSEmitter API

除此以外,SSEmitter还提供了几种API,如上面例子中使用到的:

  1. emitter.send() 方法向客户端发送消息。
  2. SseEmitter.event() 创建一个事件对象,设置事件名称和数据。
  3. emitter.complete() 表示数据发送完成后关闭连接。
  4. emitter.completeWithError(e) 在发生错误时关闭连接并报错。

3.2.3、SSEmitter注册回调

SseEmitter 可以通过注册回调函数来处理服务器端发往客户端的事件。当服务器端有新的数据需要推送给客户端时,注册的回调函数将会被调用。SSEmitter继承了ResponseBodyEmitter,提供的一系列注册回调函数有:

示例代码:

package com.example.springbootsse.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Date;


@RestController
@RequestMapping("/sse")
public class SSEmitterController {

    @GetMapping("/stream")
    public SseEmitter stream() {
        // 3S超时
        SseEmitter emitter = new SseEmitter(10000L);

        // 注册回调函数,处理服务器向客户端推送的消息
        emitter.onCompletion(() -> {
            System.out.println("Connection completed");
            // 在连接完成时执行一些清理工作
        });

        emitter.onTimeout(() -> {
            System.out.println("Connection timeout");
            // 在连接超时时执行一些处理
            emitter.complete();
        });

        // 在后台线程中模拟实时数据
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    emitter.send(SseEmitter.event().name("message").data("[" + new Date() + "] Data #" + i));
                    Thread.sleep(1000);
                }
                emitter.complete(); // 数据发送完成后,关闭连接
            } catch (IOException | InterruptedException e) {
                emitter.completeWithError(e); // 发生错误时,关闭连接并报错
            }
        }).start();

        return emitter;
    }
}
  1. onCompletion():在连接完成时候触发,可在连接完成时执行一些清理工作
  2. onTimeout():当连接超时时触发
  3. onError():当连接异常时触发
  4. completeWithError(e):用于发生错误时,关闭连接并报错

4、小结

其实SSE已经出来很久了,但是熟知他的人却很少,大多数项目中还是直接使用了websocket技术。直到最近ChatGPT火了之后,很多项目需要对接GPT进行实时推流,才逐渐又被人提起。所以借此篇文章给自己扫盲一下。

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

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

相关文章

esp32 操作DS1307时钟芯片

电气参数摘要 有VCC供电&#xff0c;IIC活动状态是1.5mA&#xff0c;待机状态200μA&#xff0c;电池电流5nA(MAX50nA&#xff09;无VCC供电的时候&#xff0c;电池电流&#xff0c;300nA&#xff08;时钟运行&#xff09;&#xff0c;10nA&#xff08;时钟停止&#xff09;供…

ASP.NET Core 7 Web 使用Session

ASP.NET Core 好像不能像20年前那样直接使用Session函数&#xff0c;我使用如下方法 1、在NuGet安装以下2个包 2、在Program.cs注册 //注册Session builder.Services.AddSession(options > {options.IdleTimeout TimeSpan.FromMinutes(60);options.Cookie.HttpOnly fals…

大小端(C语言)

一、什么是大小端&#xff1a; 1.大端(Big-Endian):高地址存放低位 2.小端(Little-Endian):高地址存放高位 例如&#xff1a;0x11223344在内存中存储 大小端影响了什么&#xff1f; 当基本数据类型占用字节数超过了1字节后&#xff0c;大小端决定了数据按照什么顺序存储在…

3. SQL 语言

重点&#xff1a; MySQL 的 三种安装方式&#xff1a;包安装&#xff0c;二进制安装&#xff0c;源码编译安装。 MySQL 的 基本使用 MySQL 多实例 DDLcreate alter drop DML insert update delete DQL select 3&#xff09;SQL 语言 3.1&#xff09;关系型数据库的常见…

C语言-算法-背包

[USACO07DEC] Charm Bracelet S&#xff08;01背包&#xff09; 题目描述 Bessie has gone to the mall’s jewelry store and spies a charm bracelet. Of course, she’d like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. E…

通过LiveNVR实现海康大华华为宇视等监控摄像头在服务器上录像存储,并web无插件直播和回放

支持云端录像服务器上面集中录像存储在部署LiveNVR的服务器上面 1、流媒体服务软件2、配置开启录像(云端录像)3、录像回看(云端录像)3.1、查看录像3.1.1、时间轴视图3.1.2、列表视图 4、云端录像相关接口5、如何分享时间轴录像回看&#xff1f;6、iframe集成示例7、RTSP/HLS/FL…

Docker 容器内运行 mysqldump 命令来导出 MySQL 数据库,自动化备份

备份容器数据库命令&#xff1a; docker exec 容器名称或ID mysqldump -u用户名 -p密码 数据库名称 > 导出文件.sql请替换以下占位符&#xff1a; 容器名称或ID&#xff1a;您的 MySQL 容器的名称或ID。用户名&#xff1a;您的 MySQL 用户名。密码&#xff1a;您的 MySQL …

数据结构——用链表实现Map

目录 一、映射&#xff08;Map&#xff09; 二、代码实现 1.建立接口 2.方法实现 &#xff08;1&#xff09;映射的建立 键&#xff08;key&#xff09;和值&#xff08;val&#xff09;的建立 重写toString方法 &#xff08;2&#xff09;构造方法 &#xff08;3&…

详解Mockito

详解Mockito 1. Mockito简介 在我们的编程世界中&#xff0c;测试是一个非常重要的环节&#xff0c;它能帮助我们确保代码的质量和稳定性。而在众多的测试方法中&#xff0c;Mock测试是一种非常有效的手段。 1.1 什么是 Mock 测试 Mock测试&#xff0c;顾名思义&#xff0c;…

夹耳式骨传导耳机怎么戴?有什么注意事项吗

骨传导耳机是靠震动我们的颅骨来播放声音的耳机&#xff0c;它的厉害之处在于不用塞进耳朵里&#xff0c;所以不会让耳朵感到堵塞。这意味着在运动、听歌、打电话等各种活动的时候&#xff0c;它们可以提供一个非常特别的体验。而骨传导耳机里有一种类别叫做夹耳式骨传导耳机&a…

2024初学编曲免费软件FL Studio21.2.2

FL Studio在业内也被称作“水果”软件&#xff0c;这是一款功能强大、简单易上手的专业编曲软件。软件中的音效插件库拥有超过25种音效插件&#xff0c;能够帮助激发我们的创作灵感。而FL Studio中文还推出了训练营课程&#xff0c;初学者可以在训练营中进行编曲知识的学习&…

Java多线程--线程的生命周期

文章目录 一、JDK1.5之前&#xff1a;5种状态五种状态1、新建2、就绪3、运行4、阻塞5、死亡 二、JDK1.5及之后&#xff1a;六种状态 Java语言使用 Thread类及其子类的对象来表示 线程&#xff0c;在它的一个完整的生命周期中通常要经历如下一些状态。 一、JDK1.5之前&#xf…

【Java面试】Mysql

目录 sql的执行顺序索引的优点和缺点怎么避免索引失效(也属于sql优化的一种)一条sql查询非常慢&#xff0c;我们怎么去排查和优化&#xff1f;存储引擎 MylSAM和InnoDB、Memory的区别事务的四大特性(ACID)脏读、不可重复读、幻读事务的隔离级别&#xff1f;怎么优化数据库SQL优…

Java中SimpleDateFormat时YYYY与yyyy以及HH和hh的区别注意踩坑

场景 Java开发手册中为什么要求SimpleDateFormat时用y表示年&#xff0c;而不能用Y&#xff1a; Java开发手册中为什么要求SimpleDateFormat时用y表示年&#xff0c;而不能用Y_simpledateformat 怎么确定y就是年-CSDN博客 在使用SimpleDateFormat在获取当前日期时因使用了YY…

【DDD】学习笔记-软件开发团队的沟通与协作

领域驱动设计的核心是“领域”&#xff0c;因此要运用领域驱动设计&#xff0c;从一开始就要让团队走到正确的点儿上。当我们组建好了团队之后&#xff0c;应该从哪里开始&#xff1f;不是 UI 原型设计、不是架构设计、也不是设计数据库&#xff0c;这些事情虽然重要但却非最高…

STM32F1之RTC实时时钟(Unix时间戳)

目录 1. Unix时间戳 2. UTC/GMT 3. 时间戳转换 3.1 time_t 3.2 struct tm 3.3 char * 3.4 时间戳的使用 实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器&#xff0c;在相应软件配置下&#xff0c;可提供时钟日历的功能。修改计数器的值可以重新设置…

leetcode hot100 组合总和Ⅲ

本题中&#xff0c;要求我们求在1-9范围内&#xff0c;满足k个数的和为n的组合&#xff08;组合是无序的&#xff0c;并且题目中要求不可以重复&#xff09;。 这种组合问题依旧需要用回溯算法来解决。因为我们没办法控制产生k层for循环。回溯算法的过程是构建树结构&#xff…

巴厘行记(二)——乌布之夜

欢迎览阅《巴厘行记》系列文章 巴厘行记巴厘行记&#xff08;一&#xff09;——海神庙 巴厘行记&#xff08;二&#xff09;——乌布之夜 巴厘行记&#xff08;三&#xff09;——Auntie和Mudi 巴厘行记&#xff08;四&#xff09;——乌布漫游 巴厘行记&#xff08;五&a…

【学网攻】 第(10)节 -- 路由器单臂路由配置

系列文章目录 目录 系列文章目录 文章目录 前言 一、单臂路由是什么&#xff1f; 二、实验 1.引入 实验拓扑图 PC配置 Sw配置 Router配置 实验验证 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 -- 交…

Qt使用中文字符串乱码的问题

文章目录 vs编译器下第一种解决方式第二种解决方式 Qt编译器下 我们在使用qt的时候有时候会遇到打印中文字符串的时候出现中文乱码的问题&#xff0c;主要是由于Qt的QString字符串存储的方式是使用utf-8的编码方式&#xff0c;如果我们本地的文件是使用GBK方式的编码再使用中文…