SSE 全称Server Sent Event
,直译一下就是服务器发送事件,一般的项目开发中,用到的机会不多,可能很多小伙伴不太清楚这个东西,到底是干啥的,有啥用
本文主要知识点如下:
- SSE 扫盲,应用场景分析
- 借助异步请求实现 sse 功能,加深概念理解
- 使用
SseEmitter
实现一个简单的推送示例
I. SSE 扫盲
对于 sse 基础概念比较清楚的可以跳过本节
1. 概念介绍
sse(Server Sent Event
),直译为服务器发送事件,顾名思义,也就是客户端可以获取到服务器发送的事件
我们常见的 http 交互方式是客户端发起请求,服务端响应,然后一次请求完毕;但是在 sse 的场景下,客户端发起请求,连接一直保持,服务端有数据就可以返回数据给客户端,这个返回可以是多次间隔的方式
2. 特点分析
SSE 最大的特点,可以简单规划为两个
- 长连接
- 服务端可以向客户端推送信息
了解 websocket 的小伙伴,可能也知道它也是长连接,可以推送信息,但是它们有一个明显的区别
sse 是单通道,只能服务端向客户端发消息;而 webscoket 是双通道
那么为什么有了 webscoket 还要搞出一个 sse 呢?既然存在,必然有着它的优越之处
sse | websocket |
---|---|
http 协议 | 独立的 websocket 协议 |
轻量,使用简单 | 相对复杂 |
默认支持断线重连 | 需要自己实现断线重连 |
文本传输 | 二进制传输 |
支持自定义发送的消息类型 | - |
3. 应用场景
从 sse 的特点出发,我们可以大致的判断出它的应用场景,需要轮询获取服务端最新数据的 case 下,多半是可以用它的
比如显示当前网站在线的实时人数,法币汇率显示当前实时汇率,电商大促的实时成交额等等…
II. 手动实现 sse 功能
sse 本身是有自己的一套玩法的,后面会进行说明,这一小节,则主要针对 sse 的两个特点长连接 + 后端推送数据
,如果让我们自己来实现这样的一个接口,可以怎么做?
1. 项目创建
借助 SpringBoot 2.2.1.RELEASE
来创建一个用于演示的工程项目,核心的 xml 依赖如下
1 | xml复制代码<parent> |
2. 功能实现
在 Http1.1 支持了长连接,请求头添加一个Connection: keep-alive
即可
在这里我们借助异步请求来实现 sse 功能,至于什么是异步请求,推荐查看博文: 【WEB 系列】异步请求知识点与使用姿势小结
因为后端可以不定时返回数据,所以我们需要注意的就是需要保持连接,不要返回一次数据之后就断开了;其次就是需要设置请求头Content-Type: text/event-stream;charset=UTF-8
(如果不是流的话会怎样?)
1 | java复制代码// 新建一个容器,保存连接,用于输出返回 |
看一下上面的实现,基本上还是异步请求的那一套逻辑,请仔细看一下callable
中的逻辑,有一个 while 循环,来保证长连接不中断
接下来我们新增两个接口,用来模拟后端给客户端发送消息,关闭连接的场景
1 | java复制代码@ResponseBody |
我们简单的来演示下操作过程
III. SseEmitter
上面只是简单实现了 sse 的长连接 + 后端推送消息,但是与标准的 SSE 还是有区别的,sse 有自己的规范,而我们上面的实现,实际上并没有管这个,导致的问题是前端按照 sse 的玩法来请求数据,可能并不能正常工作
1. sse 规范
在 html5 的定义中,服务端 sse,一般需要遵循以下要求
请求头
开启长连接 + 流方式传递
1 | yaml复制代码Content-Type: text/event-stream;charset=UTF-8 |
数据格式
服务端发送的消息,由 message 组成,其格式如下:
1 | makefile复制代码field:value\n\n |
其中 field 有五种可能
- 空: 即以
:
开头,表示注释,可以理解为服务端向客户端发送的心跳,确保连接不中断 - data:数据
- event: 事件,默认值
- id: 数据标识符用 id 字段表示,相当于每一条数据的编号
- retry: 重连时间
2. 实现
SpringBoot 利用 SseEmitter 来支持 sse,可以说非常简单了,直接返回SseEmitter
对象即可;重写一下上面的逻辑
1 | java复制代码@RestController |
上面的实现,用到了 SseEmitter 的几个方法,解释如下
send()
: 发送数据,如果传入的是一个非SseEventBuilder
对象,那么传递参数会被封装到 data 中complete()
: 表示执行完毕,会断开连接onTimeout()
: 超时回调触发onCompletion()
: 结束之后的回调触发
同样演示一下访问请求
上图总的效果和前面的效果差不多,而且输出还待上了前缀,接下来我们写一个简单的 html 消费端,用来演示一下完整的 sse 的更多特性
1 | html复制代码<!doctype html> |
将上面的 html 文件放在项目的resources/static
目录下;然后修改一下前面的SseRest
1 | java复制代码@Controller |
我们上面超时时间设置的比较短,用来测试下客户端的自动重连,如下,开启的日志不断增加
其次将 SseEmitter 的超时时间设长一点,再试一下数据推送功能
请注意上面的演示,当后端结束了长连接之后,客户端会自动重新再次连接,不用写额外的重试逻辑了,就这么神奇
3. 小结
本篇文章介绍了 SSE 的相关知识点,并对比 websocket 给出了 sse 的优点(至于啥优点请往上翻)
请注意,本文虽然介绍了两种 sse 的方式,第一种借助异步请求来实现,如果需要完成 sse 的规范要求,需要自己做一些适配,如果需要了解 sse 底层实现原理的话,可以参考一下;在实际的业务开发中,推荐使用SseEmitter
IV. 其他
0. 项目
系列博文
- 200329-SpringBoot 系列教程 web 篇之异步请求知识点与使用姿势小结
- 200105-SpringBoot 系列教程 web 篇之自定义返回 Http-Code 的 n 种姿势
- 191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition
- 191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势
- 191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势
- 191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩
- 191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇
- 191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南
- 191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver
- 191010-SpringBoot 系列教程 web 篇之全局异常处理
- 190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置
- 190929-SpringBoot 系列教程 web 篇之重定向
- 190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势
- 190905-SpringBoot 系列教程 web 篇之中文乱码问题解决
- 190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器
- 190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总
- 190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总
- 190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建
- 190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建
- 190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建
- 190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明
- 190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析
- 190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建
- 190316-Spring MVC 之基于 xml 配置的 web 应用构建
- 190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid
源码
1. 一灰灰 Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰 Blog 个人博客 blog.hhui.top
- 一灰灰 Blog-Spring 专题博客 spring.hhui.top
本文转载自: 掘金