SSE 推送技术

news2024/11/23 15:33:00

1、简介

Server-Sent Events(SSE)技术,它是一种用于实现服务器向客户端实时单向推送数据的Web技术。
SSE基于HTTP协议,允许服务器将数据以事件流(Event Stream)的形式发送给客户端。客户端通过建立持久的HTTP连接,并监听事件流,可以实时接收服务器推送的数据。
之前分享了一篇关于websocket技术的文章。本篇算是之前内容的一个补充。

官网摘要:

2、SSE和WebSocket的区别

WebSocket是另一种用于实现实时双向通信的Web技术。

  • 数据推送方面

    • SSE 是服务端像客户端的单向通信的技术。

    • WebSocket是双向通讯的技术

  • 协议方面

    • SSE是基于HTTP协议的长连接,超时后可以自动重连

    • WebSocket是基于ws协议的,建立双向连接实现通讯的

3、SSE的使用

SSE的使用无需引入特别的包,因为是一个Web技术,只要应为web对应的依赖即可。SSE的客户端是SseEmitter

@RestController
@RequestMapping("/foo")
public class FooController {

    Map<Integer,SseEmitter> map = Maps.newConcurrentMap();
    Map<Integer,SseEmitter> doneMap = Maps.newConcurrentMap();
    String curentContext = "";

    @GetMapping(value = "/sse", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter sseEmitter(HttpServletRequest request) throws IOException {
        String messageId = request.getHeader("Last-Event-ID");
        System.out.println("Last-Event-ID 重新连接:" + messageId);
        Integer sseEmitterId = RandomUtils.nextInt();
        SseEmitter sseEmitter = new SseEmitter(15000L);
        System.out.println("sseEmitter 建立连接... sseEmitterId=" + sseEmitterId);
        map.put(sseEmitterId, sseEmitter);
        if (StringUtils.isNotBlank(messageId)) {
            if (!doneMap.containsKey(Integer.valueOf(messageId))) {
                sseEmitter.send(SseEmitter.event().id(messageId).data("来自客户端【" + messageId + "】补发的信息:" + curentContext));
            }
        }

        sseEmitter.onCompletion(() -> {
            System.out.println("sseEmitter 结束... sseEmitterId=" + sseEmitterId);
            map.remove(sseEmitterId);
        });
        return sseEmitter;
    }

    @GetMapping("/sseSend")
    public String sseSend() {
        System.out.println("获取的sseEmitter客户端:" + JSON.toJSONString(map));
        curentContext = "测试SSE" + DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss");
        if (!map.isEmpty()) {
            doneMap.clear();
            map.forEach((key, value) -> {
                try {
                    value.send(SseEmitter.event().id(String.valueOf(key)).data("来自客户端【" + key + "】的信息:" + curentContext));
                    doneMap.put(key, value);
                    // value.send("来自客户端【" + key + "】的信息-----------:" + j);
                    Thread.sleep(1000);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }

        return "发送成功!";
    }

    @GetMapping("/closeWindow")
    public void test05() {
        System.out.println("closeWindow 窗口被关闭了....");
    }
}
3.1 订阅方法说明(/foo/sse)

/foo/sse 为订阅的方法。客户端打开页面,订阅该接口。返回的SseEmitter 为当前页面专属的连接,可以通过该连接推送消息。

注意事项:

  • 订阅的返回值必须是SseEmitter ,返回的数据类型为事件流。执行返回类型的的话需要配置produces = {MediaType.TEXT_EVENT_STREAM_VALUE} 。也可以不配置,请求会自动匹配。

  • 消息的发送,必须通过返回的SseEmitter,调用send()方法。由于需要实时推送,所以需要将创建的SseEmitter 缓存起来,随时推送消息。

  • SseEmitter 空参构造函数默认的超时时间为60s,也可以通过构造参数设置超时时间。案例中超时时间15s。如果设置成0,则表示永不超时。

  • Header中Last-Event-ID 参数为当前连接最新推送消息的ID,该ID可以自定义。消息推送之后,客户端重连之后,Header中会自动携带此参数(Last-Event-ID)。

  • SseEmitter 连接可以注册onCompletion【关闭】,onTimeOut【超时】,onError【错误】事件的回调。

3.2 模拟消息推送(/foo/sseSend)

/foo/sseSend 模拟消息推送的方法。获取创建的SseEmitter 连接,然后逐个推送消息。

注意事项:

  • map里面存放客户端的ID和连接。
  • 因为SSE连接会超时,超时的连接关闭之后会通过回调删除连接,所以重新连接的连接不会受到消息的推送。所以使用doneMap记录已经推送的客户端。自动连接的新连接补发消息。
3.3 页面关闭事件(/foo/closeWindow)

/foo/closeWindow 是页面被关闭时的请求连接。正常的逻辑里面,应该删除服务端保存的连接。案例中没有去实现,因为页面关闭有兼容性问题。只做演示。

  • 页面关闭的事件有兼容性问题,不能保证一定会触发

  • 页面关闭后,推送消息的连接应该被清除,否则容易引起OOM

  • 设置超时时间,通过回调可以避免这种问题。但是如果设置成永不超时,则会必须处理页面被关闭后连接的清除。

  • 关闭连接也可以使用客户端close方法直接关闭,但是如果发型消息的话会报错

    Caused by: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
        at sun.nio.ch.SocketDispatcher.write0(Native Method) ~[na:1.8.0_202]
        at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51) ~[na:1.8.0_202]
        at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93) ~[na:1.8.0_202]
        at sun.nio.ch.IOUtil.write(IOUtil.java:65) ~[na:1.8.0_202]
        at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471) ~[na:1.8.0_202]
        at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:135) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1424) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:768) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.tomcat.util.net.SocketWrapperBase.flushBlocking(SocketWrapperBase.java:732) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.tomcat.util.net.SocketWrapperBase.flush(SocketWrapperBase.java:716) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.coyote.http11.Http11OutputBuffer$SocketOutputBuffer.flush(Http11OutputBuffer.java:573) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.coyote.http11.filters.ChunkedOutputFilter.flush(ChunkedOutputFilter.java:157) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.coyote.http11.Http11OutputBuffer.flush(Http11OutputBuffer.java:221) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.coyote.http11.Http11Processor.flush(Http11Processor.java:1255) [tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:402) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.coyote.Response.action(Response.java:209) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:306) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
        ... 67 common frames omitted
    

4、客户端的使用

不需要引入任何js,直接使用EventSource 建立连接。

案例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试SSE</title>
</head>
<body>
  <h1>测试SSE</h1>
  <div id="stock-price"></div>
  <div id="closeConnect">关闭连接</div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
    let eventSource;
    function init() {
        eventSource = new EventSource('/foo/sse');
        eventSource.onmessage = function (event) {
            console.info(event);
            document.getElementById('stock-price').innerHTML = event.data;
        };

        eventSource.onerror = function (event) {
            console.info(event.data + "::::exception");
            // if (event.target.readyState === EventSource.CLOSED) {
            //     init();
            // }
        };
        eventSource.addEventListener('test', e => {
            console.log(`message-data: ${e.data}`);
        }, false);
    }
    init();

    window.onbeforeunload  = function(e) {
        $.get("/foo/closeWindow", {});
    };

    $("#closeConnect").click(function(){
        console.info("close connection");
        eventSource.close();
    });
</script>
</html>
4.1 建立连接

new EventSource('/foo/sse') 建立连接/订阅消息,页面打开,方法执行会根据订阅的路径请求服务端获取连接。

4.2 监听消息

  • eventSource.onmessage 监听推送的消息,event.data直接可以获取推送的消息。

  • eventSource.onerror 监听异常的消息

  • eventSource.close() 关闭连接

  • eventSource.addEventListener 监听自定义时间,服务端通过SseEmitter.event().name(xxx)来设置事件的名称。

4.3 页面关闭的事件

window.onbeforeunload 监听页面的关闭,但是存在兼容性问题,或者页面异常的关闭都不会触发该方法。所以此方法不可靠。

5、案例演示

5.1 客户端页面

页面的跳转,订阅/foo/sse接口,等待消息推送。

5.2 模拟消息的推送

模拟推送消息/foo/sseSend

6、小结

  • SSE的使用要注意过期时间的设置,使用了过期时间,就要考虑客户端重连的消息的丢失问题。
  • 使用了永不过期就要考虑防止客户端连接过多造成的OOM

7、参考文档

https://zh.javascript.info/server-sent-events

https://javascript.info/server-sent-events

https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events

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

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

相关文章

一些数学公式的几何意义

三角函数平方和公式&#xff1a; 三角函数中的平方和公式有三个形式&#xff1a; 第一种&#xff1a;&#xff1b; 接着两边同时除以可以得到第二种&#xff1a;; 或第一种同时除以可以得到第三种&#xff1a;。 首先我们做一个单位圆&#xff0c;我们学三角函数的时候应该…

当两界交汇:前端开发、后端开发与全栈开发的对比与选择

编程世界就像一座大城市&#xff0c;前端开发和后端开发就像城市的两个不同街区。在这两个街区&#xff0c;前端和后端开发都有自己的价值和机会。 一、引言 有些人更喜欢在前端创造令人印象深刻的用户界面&#xff0c;而有些人更喜欢处理数据和系统逻辑。在选择时&#xff…

Topaz Gigapixel AI6.3.2(图片无损放大)

Topaz Gigapixel AI是一款功能实用的图像无损放大工具。它的特色之处在于&#xff0c;通过使用先进的深度学习方法&#xff0c;它能够将照片放大高达600%&#xff0c;同时完美保留图像的质量。 此外&#xff0c;Topaz Gigapixel AI还具有一些其他的特色功能。 它能自动进行面…

ABB DDC779BE02 3BHE006805R0002 控制主板模块

ABB DDC779BE02 3BHE006805R0002 控制主板模块用于自动化和控制系统中&#xff0c;它们可能具有以下一些常见特点和功能&#xff1a; 处理能力&#xff1a;ABB DDC779BE02 3BHE006805R0002 控制主板模块通常具有强大的处理能力&#xff0c;可以执行复杂的控制算法和逻辑。 多种…

软件测试之网站测试怎么做?有什么作用?

网站测试是指对一个已经搭建好的网站进行功能、性能、安全等方面的测试。作为一家专注于软件测试的公司&#xff0c;我们清楚地知道网站测试在整个软件开发过程中的重要性。   一、网站测试怎么做?   1、确保测试环境的稳定和一致性&#xff0c;包括操作系统、浏览器版本等…

AnV-X6使用及总结

目录 1 简介2 安装3 基础概念3.1 画布Graph3.2 基类Cell3.3 节点Node3.4 边Edge 4 使用4.1 创建节点4.2 节点连线4.3 事件系统 5 总结 1 简介 AntV是一个数据可视化&#xff08;https://x6.antv.antgroup.com/&#xff09;的工具&#xff08;https://antv.vision/zh/ &#xf…

IPv6的主要优势有哪些?

第一&#xff0c;明显地扩大了地址空间。IPv6采用128位地址长度&#xff0c;几乎可以不受限制地提供IP地址&#xff0c;从而确保了端到端连接的可能性。 第二&#xff0c;提高了网络的整体吞吐量。由于IPv6的数据包可以远远超过64k字节&#xff0c;应用程序可以利用最大传输单元…

RFID技术引领汽车零部件加工新时代

RFID技术的兴起引领了汽车零部件加工领域的新时代&#xff0c;作为一种利用无线电频率进行自动识别的技术&#xff0c;RFID技术能够快速、准确地识别物体并获取相关数据&#xff0c;在汽车零部件加工中&#xff0c;RFID技术具有重要的应用价值&#xff0c;可以提高生产效率、降…

idea环境下如何打包可运行jar?

工作中有时候偶尔写一些工具类、小程序&#xff0c;可是java程序员制作一个可运行jar实在折腾&#xff0c;利用idea开发环境&#xff0c;可以快速打包自己的可运行jar。具体怎么操作呢&#xff1f; 创建一个空白的java项目并完成自己的程序开发 完成java代码&#xff1a; /**…

Vue以及整合ElementUI

初始化vue项目 #vue 脚手架使用 webpack 模板初始化一个 appname 项目 vue init webpack appname启动 vue 项目 #项目的 package.json 中有 scripts&#xff0c;代表我们能运行的命令 npm start npm run dev #启动项目 npm run build&#xff1a;将项目打包项目结构 运行流程…

【AI视野·今日Robot 机器人论文速览 第四十二期】Wed, 27 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Wed, 27 Sep 2023 Totally 48 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;***Tactile Estimation of Extrinsic Contact,基于触觉的外部接触估计与稳定放置 (from 三菱电机) Daily Robotics Pape…

这才是连锁商店的必杀技,你的太Low了!

随着社会的不断发展和技术的进步&#xff0c;视频监控系统在各个领域的应用变得越来越广泛。无论是商业、政府、教育还是个人领域&#xff0c;视频监控系统都扮演着重要的角色。 此外&#xff0c;视频监控系统不仅提供了安全性和保护&#xff0c;还为各种管理和监测任务提供了强…

华为云云耀云服务器L实例评测 | 实例评测使用之硬件参数评测:华为云云耀云服务器下的硬件参数查询

华为云云耀云服务器L实例评测 &#xff5c; 实例评测使用之硬件参数评测&#xff1a;华为云云耀云服务器下的硬件参数查询 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀…

网络安全(黑客技术)自学内容

前言 一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防…

MySQL - DML数据增删改

功能介绍&#xff1a; DML&#xff08;Data Manipulation Language&#xff09;数据操作语言&#xff0c;用来对数据库中表的数据记录进 行增、删、改操作。 添加数据&#xff08;INSERT&#xff09; 基本语法&#xff1a;insert into 表名(字段列表) values (值列表); …

数据分发服务(DDS, Data Distribution Service)简介

什么是DDS &#xff1f; 工业物联网成熟的数据连接标准 OMG 数据分发服务 (DDS™) 是一个中间件协议和 API 标准&#xff0c;用于来自 Object Management Group (OMG) 的以数据为中心的连接。它将系统的组件集成在一起&#xff0c;提供业务和关键任务物联网 (IoT) 应用程序所…

一招教你控制python多线程的线程数量

大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 在使用python的多线程爬虫&#xff0c;当时爬取一个图片网站&#xff0c;开启多线程后&#xff0c;并没有限制线程的数量&#xff0c; 也就是说&#xff0c;如果下载1000张…

SAP PO运维(五):系统用户授权

1、访问 SAP PO 服务器和用户管理 访问服务器:http://hostname:port/startPage然后选择“用户管理” 2、创建新用户账号 3、授予权限

郁金香2021年游戏辅助技术(初级班)(中)

郁金香2021年游戏辅助技术初级班&#xff08;中&#xff09; MFC动态链接库与注入DLL在目标进程分配内存写入代码向目标进程注入代码加载DLL029-分析角色对象的属性外平栈的call计算参数数量 C,C编写代码读取对象属性值C,C输入输出重定向C,C定时器与主线程定时器&#xff08;微…

Spring Controller内存马

获取当前上下文运行环境 getCurrentWebApplicationContext WebApplicationContext context ContextLoader.getCurrentWebApplicationContext(); 在SpringMVC环境下获取到的是一个XmlWebApplicationContext类型的Root WebApplicationContext&#xff1a; 在Spring MVC环境中…