链接: SpringBoot 实现SSE 服务器发送事件
链接: SpringBoot 实现SSE 服务器发送事件
链接: Springboot之整合SSE实现消息推送
链接: springboot SseEmitter 消息推送
链接: 在springboot中使用Sse(Server-sent Events)Web实时通信技术-服务器发送事件SseEmitter
链接: Web 实时消息推送详解

链接: SSE(Server-Send Events)实战

链接: Server side event (SSE)实现消息推送功能

什么是sse

链接: 著作权归所有原文链接
服务器发送事件(Server-Sent Events),简称 SSE。这是一种服务器端到客户端(浏览器)的单向消息推送。SSE 基于 HTTP 协议的,我们知道一般意义上的 HTTP 协议是无法做到服务端主动向客户端推送消息的,但 SSE 是个例外,它变换了一种思路。

SSE 在服务器和客户端之间打开一个单向通道,服务端响应的不再是一次性的数据包而是text/event-stream类型的数据流信息,在有数据变更时从服务器流式传输到客户端。整体的实现思路有点类似于在线视频播放,视频流会连续不断的推送到浏览器,你也可以理解成,客户端在完成一次用时很长(网络不畅)的下载。

SSE 与 WebSocket 作用相似,都可以建立服务端与浏览器之间的通信,实现服务端向客户端推送消息,但还是有些许不同:

  • SSE 是基于 HTTP 协议的,它们不需要特殊的协议或服务器实现即可工作;WebSocket 需单独服务器来处理协议。
  • SSE 单向通信,只能由服务端向客户端单向通信;
    WebSocket 全双工通信,即通信的双方可以同时发送和接受信息。
  • SSE 实现简单开发成本低,无需引入其他组件;
    WebSocket 传输数据需做二次解析,开发门槛高一些。
  • SSE 默认支持断线重连;WebSocket 则需要自己实现。
  • SSE 只能传送文本消息,二进制数据需要经过编码后传送;WebSocket 默认支持传送二进制数据。

注意: SSE 不支持 IE 浏览器,对其他主流浏览器兼容性做的还不错。

SSE 与 WebSocket 该如何选择?

链接: 著作权归所有原文链接
SSE 好像一直不被大家所熟知,一部分原因是出现了 WebSocket,这个提供了更丰富的协议来执行双向、全双工通信。对于游戏、即时通信以及需要双向近乎实时更新的场景,拥有双向通道更具吸引力。但是,在某些情况下,不需要从客户端发送数据。而你只需要一些服务器操作的更新。比如:站内信、未读消息数、状态更新、股票行情、监控数量等场景,SEE 不管是从实现的难易和成本上都更加有优势。此外,SSE 具有 WebSocket 在设计上缺乏的多种功能,例如:自动重新连接、事件 ID 和发送任意事件的能力。

sse 规范

在 html5 的定义中,服务端 sse,一般需要遵循以下要求

  • 请求头
开启长连接 + 流方式传递
Content-Type: text/event-stream;charset=UTF-8
Cache-Control: no-cache
Connection: keep-alive
  • 数据格式

服务端发送的消息,由 message 组成,其格式如下:

field:value\n\n

其中 field 有五种可能

  • 空: 即以:开头,表示注释,可以理解为服务端向客户端发送的心跳,确保连接不中断
  • data:数据。订阅后,服务端在消息可用时立即发送给客户端。事件是采用 UTF-8 编码的文本消息。事件之间由两个换行符分隔\n\n。每个事件由一个或多个名称:值字段组成,由单个换行符\n 分隔。 ​
  • event: 事件,默认值
  • id: 数据标识符 id 字段表示,相当于每一条数据的编号 。服务器可以发送唯一的事件标识符(id字段)。如果连接中断,客户端会自动重新连接并发送最后接收到的带有header的 Last-Event-ID 的事件 ID。 ​
  • retry: 重连时间 ,在重试字段中,服务器可以发送超时(以毫秒为单位),之后客户端应在连接中断时自动重新连接。如果未指定此字段,则标准应为 3000 毫秒。

后端

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** sse服务* 客户端关闭时,只能等待超时 服务端断开连接*/
@Controller
@RequestMapping(path = "sse")
public class SseRest {private final static Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>();/*** 连接sse服务* @param id* @return* @throws IOException*/@GetMapping(path = "subscribe", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})public SseEmitter push(String id) throws IOException {// 超时时间设置为5分钟,用于演示客户端自动重连SseEmitter sseEmitter = new SseEmitter(5_60_000L);// 设置前端的重试时间为1s// send(): 发送数据,如果传入的是一个非SseEventBuilder对象,那么传递参数会被封装到 data 中sseEmitter.send(SseEmitter.event().reconnectTime(1000).data("连接成功"));sseCache.put(id, sseEmitter);System.out.println("add " + id);sseEmitter.send("你好", MediaType.APPLICATION_JSON);SseEmitter.SseEventBuilder data = SseEmitter.event().name("finish").id("6666").data("哈哈");sseEmitter.send(data);// onTimeout(): 超时回调触发sseEmitter.onTimeout(() -> {System.out.println(id + "超时");sseCache.remove(id);});// onCompletion(): 结束之后的回调触发sseEmitter.onCompletion(() -> System.out.println("完成!!!"));return sseEmitter;}/*** http://127.0.0.1:8080/sse/push?id=7777&content=%E4%BD%A0%E5%93%88aaaaaa* @param id* @param content* @return* @throws IOException*/@ResponseBody@GetMapping(path = "push")public String push(String id, String content) throws IOException {SseEmitter sseEmitter = sseCache.get(id);if (sseEmitter != null) {sseEmitter.send(content);}return "over";}@ResponseBody@GetMapping(path = "over")public String over(String id) {SseEmitter sseEmitter = sseCache.get(id);if (sseEmitter != null) {// complete(): 表示执行完毕,会断开连接sseEmitter.complete();sseCache.remove(id);}return "over";}
}

前端

有表示连接状态的 readyState 属性:
EventSource.CONNECTING = 0 - 连接尚未建立,或已关闭且客户端正在重新连接
EventSource.OPEN = 1 - 客户端有一个打开的连接并在接收到事件时处理它们
EventSource.CLOSED = 2- 连接未打开,并且客户端未尝试重新连接,要么出现致命错误,要么调用了 close() 方法

要处理连接的建立,它应该订阅 onopen 事件处理程序。eventSource.onopen = function () {console.log('connection is established');};
为了处理连接状态的一些异常或致命错误,它应该订阅 onerrror 事件处理程序。eventSource.onerror = function (event) {console.log('connection state: ' + eventSource.readyState + ', error: ' + event);};
客户端接收消息并处理他们,可以使用onmessage方法eventSource.onmessage = function (event) {console.log('id: ' + event.lastEventId + ', data: ' + event.data);
};
<!doctype html>
<html lang="en">
<head><title>Sse测试文档</title>
</head>
<body>
<div>sse测试</div>
<div id="result"></div>
</body>
</html>
<script>let source = null;let userId = 7777if (window.EventSource) {// 建立连接source = new EventSource('http://localhost:8080/sse/subscribe?id='+userId);console.log("连接用户=" + userId);/*** 连接一旦建立,就会触发open事件* 另一种写法:source.onopen = function (event) {}*/source.addEventListener('open', function (e) {console.log("建立连接。。。");}, false);/*** 客户端收到服务器发来的数据* 另一种写法:source.onmessage = function (event) {}*/source.addEventListener('message', function (e) {console.log(e.data);});source.addEventListener('finish', function (e) {console.log(e.id);console.log(e.data);});} else {console.log("你的浏览器不支持SSE");}
</script>

上面的实现,用到了 SseEmitter 的几个方法,解释如下
send(): 发送数据,如果传入的是一个非SseEventBuilder对象,那么传递参数会被封装到 data 中
complete(): 表示执行完毕,会断开连接
onTimeout(): 超时回调触发
onCompletion(): 结束之后的回调触发

在实际的业务开发中,推荐使用SseEmitter,SseEmitter已经帮我们把这些封装好了

注意

nginx 配置 proxy_buffering off
不配置proxy_buffering off的话,会出现请求发出后,接口收到直接返回,无法保持长连接。
参考网上说明:proxy_buffering这个参数用来控制是否打开后端响应内容的缓冲区,如果这个设置为off,那么proxy_buffers和proxy_busy_buffers_size这两个指令将会失效。

问题描述
我正在使用eventSource API,并将addEventListener()添加到eventsouce中.事件源被激活直到仅45秒.我想保持连接状态,直到服务器将响应发送回客户端.

现在,我收到以下异常消息,因为直到45秒,服务器都没有响应.

EXCEPTION: No activity within 450000 milliseconds. Reconnecting

例外:450000毫秒内没有活动.重新连接.

解决方案是定期发送数据,即使是空字节也可以正常工作,并且可以使连接保持活动状态.

如果无法建立连接,而您想重试连接,则可以使用例如设置为45秒的setTimeout.

建立连接后,请使用clearTimeout停止尝试.

springboot整合sse相关推荐

  1. SpringBoot第九篇: springboot整合Redis

    这篇文章主要介绍springboot整合redis,至于没有接触过redis的同学可以看下这篇文章:5分钟带你入门Redis. 引入依赖: 在pom文件中添加redis依赖: <dependen ...

  2. es springboot 不设置id_原创 | 一篇解决Springboot 整合 Elasticsearch

    ElasticSearch 结合业务的场景,在目前的商品体系需要构建搜索服务,主要是为了提供用户更丰富的检索场景以及高速,实时及性能稳定的搜索服务. ElasticSearch是一个基于Lucene的 ...

  3. springboot整合shiro使用shiro-spring-boot-web-starter

    此文章仅仅说明在springboot整合shiro时的一些坑,并不是教程 增加依赖 <!-- 集成shiro依赖 --> <dependency><groupId> ...

  4. db2 springboot 整合_springboot的yml配置文件通过db2的方式整合mysql的教程

    springboot整合MySQL很简单,多数据源就master,slave就行了,但是在整合DB2就需要另起一行,以下是同一个yml文件 先配置MySQL,代码如下 spring: datasour ...

  5. 九、springboot整合rabbitMQ

    springboot整合rabbitMQ 简介 rabbitMQ是部署最广泛的开源消息代理. rabbitMQ轻量级,易于在内部和云中部署. 它支持多种消息传递协议. RabbitMQ可以部署在分布式 ...

  6. 八、springboot整合Spring Security

    springboot整合Spring Security 简介 Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架.它是保护基于Spring的应用程序的事实标准. Spr ...

  7. 六、springboot整合swagger

    六.springboot整合swagger 简介 swagger 提供最强大,最易用的工具,以充分利用OpenAPI规范. 官网 : https://swagger.io/ 准备工作 pom.xml ...

  8. SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例(转)...

    SpringBoot整合mybatis.shiro.redis实现基于数据库的细粒度动态权限管理系统实例 shiro 目录(?)[+] 前言 表结构 maven配置 配置Druid 配置mybatis ...

  9. SpringBoot整合RabbitMQ-整合演示

    本系列是学习SpringBoot整合RabbitMQ的练手,包含服务安装,RabbitMQ整合SpringBoot2.x,消息可靠性投递实现等三篇博客. 学习路径:https://www.imooc. ...

最新文章

  1. 【 Linux 】通用的vim简单配置方法
  2. 网页中Javascript代码的应用方式
  3. 杭州成都场「PPT 下载」新鲜出炉 | 神策 2019 数据驱动大会
  4. opencv 通过网络连接工业相机_单目摄像机测距(python+opencv)
  5. 关闭 启动_Steam如何关闭开机自动启动
  6. matplotlib 高阶之Transformations Tutorial
  7. 锁存器的工作原理_数字电路学习笔记(十):更多锁存器和触发器
  8. HTML阅读位置,script在HTML文档中位置
  9. C/C++开发者必不可少的15款编译器+IDE
  10. 【算法1-2】排序(今天刷洛谷了嘛)
  11. 字符串替换(C++)
  12. Android之利用SharedPreferences进行简单数据存储
  13. Maven: git-commit-id-plugin插件
  14. 机器学习:用正规方程法求解线性回归
  15. 《无痛苦N-S方程笔记》第二章知识点框架
  16. kali linux 无线攻击——aircrack-ng
  17. Linux磁盘管理(添加磁盘,分区、删除分区、格式化、挂载、卸载)
  18. Android重力感应
  19. Pycharm:解决点击重运行(Rerun)没有反应
  20. 阿里云交互式分析与Presto对比分析及使用注意事项

热门文章

  1. SQL实战39.针对上面的salaries表emp_no字段创建索引idx_emp_no,查询emp_no为10005,
  2. 虚拟化1-创建虚拟机
  3. 通过STM32 stlink utility工具对ST-LINK芯片信息进行读取和升级以及SWD烧录媒介
  4. macbook黑屏_MacBook维修 苹果笔记本电脑进水不开机
  5. 电脑文件管理,教你一键将大量文件夹名称翻译成英文
  6. CAJViewer显示乱码的解决方法
  7. (附源码)ssm高校实验室系统 毕业设计 800008
  8. iPhone手机屏幕三种故障及维修方法
  9. ANSYS APDL经典界面如何导入多个材料模型
  10. c/c++判断数组中元素的个数