流式编程及Spring中SSE实现

目录

[1. 流式编程](#1. 流式编程)

[1.1 SSE协议介绍](#1.1 SSE协议介绍)

[1.2 SSE数据格式](#1.2 SSE数据格式)

[2. Spring中SSE实现](#2. Spring中SSE实现)

[2.1 未设置retry同步发送响应](#2.1 未设置retry同步发送响应)

[2.2 设置retry同步发送响应](#2.2 设置retry同步发送响应)

[2.3 自定义事件](#2.3 自定义事件)

[2.3.1 终止连接](#2.3.1 终止连接)

[2.4 SSE与流式编程结合应用](#2.4 SSE与流式编程结合应用)

[2.4.1 Flux的概念](#2.4.1 Flux的概念)

[2.4.2 Flux的快速使用](#2.4.2 Flux的快速使用)

使用Flux:

常见操作符:

坑点1:

Flux实现前后端交互:

坑点2:


1. 流式编程

1.1 SSE协议介绍

先回想HTTP协议。HTTP协议本身是无状态的请求-响应模式,也就是说服务端 不会保存任何客户端 请求历史或身份状态,当服务端处理完一次请求后,就会彻底忘掉这个客户端。因此服务端无法主动向客户端推送数据。

SSE是基于HTTP协议的一种轻量级实时通信协议。它可实现流式传输,允许服务器主动向客户端(浏览器)推送数据。 (Server-Sent Events 服务器发送事件,简称SSE)

SSE协议核心特点:

  • 基于HTTP协议

复用标准HTTP/HTTPS协议,无需额外端口或协议,兼容性好易于部署

  • 单向通信机制

SSE仅支持服务端向客户端的单向数据推送。客户端先通过HTTP请求建立连接后,服务器可持续发送数据流,但客户端无法通过同一连接向服务器发送数据。

  • 自动重连机制

支持断线重连,连接中断时,浏览器会自动尝试重新连接(支持retry字段自定义重连间隔)

  • 自定义消息类型

客户端发起请求后,服务器要想保持连接开放,响应头设置为Content-Type: text/event-stream ,标识为事件流格式,持续推送事件流。

HTTP和SSE:

1.2 SSE数据格式

服务端向客户端(浏览器)发送SSE数据需设置响应头信息。

Content-Type: text/event-stream;charset=utf-8


每一次发送的信息,由若干个message组成,每个message之间由\n\n分隔,每个message内部由若干行组成,每一行都是如下格式

field: value\n

Field 可以取值为:

  • data(必需):数据内容
  • event(非必需):表示自定义的事件类型,默认是message事件
  • id(非必需):数据标识符,相当于每一条数据的编号
  • retry(非必需):指定浏览器重新发起连接的时间间隔

除此之外,还可以有冒号 **:**开头的行,表示注解。

数据示例:

: 以\n\n分隔message
event: foo\n //foo事件,事件名可以自定义
data: a foo event\n\n
data: an unnamed event\n\n //默认为message事件
event: end\n //自定义的end事件
data: a bar event\n\n


notice :message是通过一个或多个键值对拼接而成,每个message以\n\n结尾分隔,后端发送的响应可包含多个message,而message事件是默认的事件类型。

流式编程只通过文字阐述,很难表达清楚,下面我将通过Java前后端交互体现。


2. Spring中SSE实现

2.1 未设置retry同步发送响应

(代码在git:spring-ai-first-term-practice · 巨蛇神/JavaEE进阶 - 码云 - 开源中国)

  1. 需要编写前端代码,下面是代码前端解读。
  2. new EventSource(url),就是建立并维护一条到服务端的 SSE 长连接,自动接收服务端推送过来的消息。
  3. eventSource.onmessage = function(event){...},onmessage是专门用来监听message事件的,其他事件监听不到,如果后端向前端发送的信息中含有message事件的message,就执行function函数,且该函数的形参接收的实参为整个message。
  4. document.getElementById("sse").innerHTML=event.data;利用形参取出data:并赋值给给id名为sse的标签。

浏览器访问index.html,可以看到连续打印10次时间,然后间隔几秒又自动向后端发送请求,尝试重连。

2.2 设置retry同步发送响应

通过fiddle抓包:

2.3 自定义事件

message类型有两类:

|------------|--------------------|--------------------------------------|
| 消息类型 | 服务端格式 | 前端监听方式 |
| 默认消息‌ | 只有 data:,没有 event: | onmessage 能收到 |
| ‌自定义事件 | 有 event: 事件名 | onmessage ‌收不到‌,必须用 addEventListener |


2.3.1 终止连接

后端把有效数据都发给前端后,服务器最后发一个响应告诉浏览器此次连接终止,连接关闭,浏览器不会尝试重连。

2.4 SSE与流式编程结合应用

Spring 5(Spring Boot 5)开始可以使用WebFlux实现SSE。Flux是WebFlux的核心API。

2.4.1 Flux的概念

  • 可以把Flux想象成一条传送带
  1. 传送带上有许多数据,把数据看成物品
  2. 我们可以对物品进行加工,比如:过滤、修改(修改数据)
  3. 物品逐个的到达,不用等待全部到齐(也就是支持异步)
  4. 对端,也就是接收物品的一方可以调速(也就是背压机制)

2.4.2 Flux的快速使用

Flux的流程分为三个步骤:

1.创建Flux

创建一个Flux数据流,并有数据源。

2.处理数据

使用操作符对数据进行处理。

3.订阅数据

订阅Flux来消费数据,触发数据的流动。不订阅(消费数据),数据无法流动,你就取不到任何数据。


使用Flux:

notice:

创建一个Flux流,不管Flux内做了什么加工操作(.map或.delayElements等),这个Flux就只是一张"数据蓝图",只有订阅数据,Flux才会活起来,才会输出数据。

上图用Flux.just()创建包含指定元素的Flux流,还可用Flux.formIterable()和Flux.range()等。

// 从集合 ( List Set) 创建
Flux. fromIterable (Arrays. asList ( 1 , 2 , 3 ))
// ⽣成 1~5 Flux
Flux.range( 1 , 5 )


常见操作符:

|---------------|------------------|------------------------------------------|
| 操作 | 作用 | 示例代码片段 |
| map() | 元素一对一转换 | .map(String::toUpperCase) |
| filter() | 条件过滤 | .filter(s->s.length()>5) //筛选出长度大于5的 |
| take() | 限制元素数量 | .take(2) //只取前两个元素 |
| merge() | 合并多个Flux(不保证顺序) | Flux.merge(Flux.just("A"),Flux.just("B)) |
| concat() | 顺序拼接多个Flux(保证顺序) | Flux.merge(Flux.just("A"),Flux.just("B)) |
| delayElements | 延迟元素发射 | .delayElements(Duration.ofseconds(1)) |

坑点1:

Flux实现前后端交互:
坑点2:

看了"Flux实现前后端交互 "的代码,你可能产生疑惑:"Flux使用流程包含1.创建含有数据源的Flux流,2.处理数据,3.订阅数据",而"**Flux实现前后端交互"**中没使用函数进行订阅,前端还能正常获取Flux流中的数据,现在你百思不得其解。

其实这里是伟大的Spring WebFlux框架在自动帮你订阅。只有在controller层函数返回的Flux会被框架订阅,其他任何地方都不会被管理。

相关推荐
wechatbot8881 小时前
SpringBoot Vue 企业微信多账号托管|扫码登录 代理 IP 消息回调
大数据·后端·微信·企业微信·ai编程
随遇而安zx1 小时前
【多线程】---ConcurrentHashMap 源码解析
java·多线程·并发
不才不才不不才1 小时前
Spring 源码系列(27): @Transactional 七大失效场景与源码归因
java·后端·spring
随遇而安zx1 小时前
【多线程】---线程池 ThreadPoolExecutor 原理与调优
java·线程池·多线程
抠脚小弟1 小时前
WebSocketServer 使用详解:从入门到实战
java·spring·websocketserver
妙码生花1 小时前
PHP 各框架下和 Go 的性能比较
前端·后端·go
随便做点啥1 小时前
32卡64G-910B4-16后端集群部署报告
java·大数据·运维
名字还没想好☜2 小时前
Python 字符编码实战:encode/decode、UnicodeDecodeError 与 open 的 encoding 坑
开发语言·后端·python·编程语言
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之字段验证与自定义验证注解实战
java·算法·spring