目录
[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的快速使用)
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进阶 - 码云 - 开源中国)
- 需要编写前端代码,下面是代码前端解读。
- new EventSource(url),就是建立并维护一条到服务端的 SSE 长连接,自动接收服务端推送过来的消息。
- eventSource.onmessage = function(event){...},onmessage是专门用来监听message事件的,其他事件监听不到,如果后端向前端发送的信息中含有message事件的message,就执行function函数,且该函数的形参接收的实参为整个message。
- 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想象成一条传送带
- 传送带上有许多数据,把数据看成物品
- 我们可以对物品进行加工,比如:过滤、修改(修改数据)
- 物品逐个的到达,不用等待全部到齐(也就是支持异步)
- 对端,也就是接收物品的一方可以调速(也就是背压机制)

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会被框架订阅,其他任何地方都不会被管理。