服务端推送技术 Server-sent Events springBoot代码示例

简介: 服务端推送技术 Server-sent Events springBoot代码示例

 SSE推送技术

SSE全称Server-sent Events,是HTML 5 规范的一个组成部分,具体去MDN网站查看相关文档。该规范十分简单,

SSE推送技术是服务器端与浏览器端之间的通讯协议,通讯协议是基于纯文本的简单协议。

服务器端的响应的内容类型是“text/event-stream”。响应文本的内容可以看成是一个事件流,由不同的事件所组成。每个事件由类型和数据两部分组成,同时每个事件可以有一个可选的标识符。不同事件的内容之间通过仅包含回车符和换行符的空行(“rn”)来分隔。每个事件的数据可能由多行组成。

image.gif编辑

如上图所示,每个事件之间通过空行来分隔。每一行都是由键值对组成。如果键为空则表示该行为注释,会在处理时被忽略。例如第10行。第1行表示一个只包含数据的事件。会按照默认事件走(message事件)。第3-4代表一个附带eventID的事件。第6-8代表一个自定义事件。第10-14代表一个多行数据事件,多行数据由换行符链接

key定义有以下几种:

    • data,表示该行包含的是数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
    • 类型为 event,表示该行用来声明事件的类型。浏览器在收到数据时,会产生对应类型的事件。默认提供三个标准事件(当然你可以自定义):

    image.gif编辑

      • id,表示该行用来声明事件的标识符。服务器端返回的数据中包含了事件的标识符,浏览器会记录最近一次接收到的事件的标识符。如果与服务器端的连接中断,当浏览器端再次进行连接时,会通过 HTTP 头“Last-Event-ID”来声明最后一次接收到的事件的标识符。服务器端可以通过浏览器端发送的事件标识符来确定从哪个事件开始来继续连接。
      • retry,表示该行用来声明浏览器在连接断开之后进行再次连接之前的等待时间。

      SSE只适用于高级浏览器,但是注意IE不直接支持。IE上的XMLHttpRequest对象不支持获取部分的响应内容,所以不支持。每次总有IE怪不得快被淘汰了。

      SSE VS Websocket

        • SSE 只能Server到Client单项,而Websocket是双向通信。
        • SSE 比 Websocket 轻量。当然功能要简单的多。开发便利,不牵涉协议升级问题。
        • SSE 天然支持断线重连

        服务端代码示例

        import com.alibaba.fastjson.JSONObject;
        import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
        import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
        import com.hxtx.spacedata.common.domain.ResponseDTO;
        import com.hxtx.spacedata.domain.entity.task.TaskInfoEntity;
        import com.hxtx.spacedata.enums.task.TaskInfoStatusEnum;
        import com.hxtx.spacedata.mapper.task.TaskInfoDao;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.scheduling.annotation.Scheduled;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PathVariable;
        import org.springframework.web.bind.annotation.RestController;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpSession;
        import java.util.Iterator;
        import java.util.List;
        import java.util.Map;
        import java.util.concurrent.ConcurrentHashMap;
        import java.util.stream.Collectors;
        /**
         * 服务端推送技术 server-sent events
         * @description
         * @author tarzan Liu
         * @version 1.0.0
         * @date 2020/10/27
         */
        @RestController
        @Slf4j
        public class SSEController {
            @Autowired
            private TaskInfoDao taskInfoDao;
            private static ConcurrentHashMap<String,Long> ssePushUsers = new ConcurrentHashMap<>();
            /**
             *  如果没有客户端,则直接修改消息已发送 (2分钟执行一次)
             * @author sunboqiang
             * @date 2020/11/3
             */
            @Scheduled(cron = "0 0/2 * * * ?")
            public void finishSend() {
                if(ssePushUsers.size()==0){
                    QueryWrapper<TaskInfoEntity> queryWrapper = new QueryWrapper<>();
                    queryWrapper.lambda().eq(TaskInfoEntity::getStatus, TaskInfoStatusEnum.SUCCESS.getStatus());
                    queryWrapper.lambda().eq(TaskInfoEntity::getSendStatus,0);
                    List<TaskInfoEntity> list = taskInfoDao.selectList(queryWrapper);
                    if(CollectionUtils.isNotEmpty(list)){
                        taskInfoDao.updateSendStatusByIds(list.stream().map(TaskInfoEntity::getId).collect(Collectors.toList()), 2);
                    }
                }
            }
            /**
             *  剔除关闭的客户端
             * @author sunboqiang
             * @date 2020/11/3
             */
            @Scheduled(cron = "0/2 * * * * ?") // 2S执行一次
            public void clear() {
                //2秒执行一次,时间差>5S 说明客户端关闭了,直接剔除
                long now = System.currentTimeMillis();
                for (Iterator<Map.Entry<String, Long>> it = ssePushUsers.entrySet().iterator(); it.hasNext(); ) {
                    Map.Entry<String, Long> item = it.next();
                    long time = item.getValue();
                    //log.info(item.getKey()+"注册时间差:"+(now - time)/1000);
                    if(now - time > 5000){
                        //5 秒
                        it.remove();
                        log.info("剔除客户端:"+item.getKey());
                    }
                }
            }
            @GetMapping(value="/sse/push/version/get")
            public String getVersion(HttpServletRequest request){
                HttpSession session = request.getSession();
                if(null != session){
                    return session.getId();
                }
                return null;
            }
            /**
             *  推送C++ json文件编译情况信息
             * @author sunboqiang
             * @date 2020/10/29
             */
            @GetMapping(value="/sse/push/{version}",produces="text/event-stream;charset=utf-8")
            public String push(@PathVariable("version") String version) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                QueryWrapper<TaskInfoEntity> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().eq(TaskInfoEntity::getStatus, TaskInfoStatusEnum.SUCCESS.getStatus());
                queryWrapper.lambda().eq(TaskInfoEntity::getSendStatus,0);
                List<TaskInfoEntity> list = taskInfoDao.selectList(queryWrapper);
                String data = "";
                if(CollectionUtils.isEmpty(list)){
                    //还没有消息,收集等待推送的客户端
                    ssePushUsers.put(version,System.currentTimeMillis());
                    //data = "data:没有编译消息,当前打开客户端数量:"+ ssePushUsers.size()+"个;" +"\n\n";
                } else {
                    List<Long> drawingIds = list.stream().map(TaskInfoEntity::getDrawingId).distinct().collect(Collectors.toList());
                    //编译成功,推送消息
                    if(ssePushUsers.size()>0){
                        //存在接收客户端
                        ResponseDTO result = new ResponseDTO();
                        result.setCode(1);
                        result.setMsg("有新的编译");
                        result.setSuccess(true);
                        result.setData("drawingIds="+drawingIds);
                        data = "data:"+ JSONObject.toJSONString(result) +"\n\n";
                        ssePushUsers.remove(version);
                        if(ssePushUsers.size() == 0){
                            //最后一个客户端推送完成
                            taskInfoDao.updateSendStatusByIds(list.stream().map(TaskInfoEntity::getId).collect(Collectors.toList()), 1);
                        }
                    } else {
                        //没有客户端,直接推送成功
                        taskInfoDao.updateSendStatusByIds(list.stream().map(TaskInfoEntity::getId).collect(Collectors.toList()), 1);
                    }
                }
                return data;
            }
        }

        image.gif


        相关文章
        |
        14天前
        |
        开发框架 负载均衡 Java
        当热门技术负载均衡遇上 Spring Boot,开发者的梦想与挑战在此碰撞,你准备好了吗?
        【8月更文挑战第29天】在互联网应用开发中,负载均衡至关重要,可避免单服务器过载导致性能下降或崩溃。Spring Boot 作为流行框架,提供了强大的负载均衡支持,通过合理分配请求至多台服务器,提升系统可用性与可靠性,优化资源利用。本文通过示例展示了如何在 Spring Boot 中配置负载均衡,包括添加依赖、创建负载均衡的 `RestTemplate` 实例及服务接口调用等步骤,帮助开发者构建高效、稳定的应用。随着业务扩展,掌握负载均衡技术将愈发关键。
        39 6
        |
        11天前
        |
        Java 数据库连接 数据库
        告别繁琐 SQL!Hibernate 入门指南带你轻松玩转 ORM,解锁高效数据库操作新姿势
        【8月更文挑战第31天】Hibernate 是一款流行的 Java 持久层框架,简化了对象关系映射(ORM)过程,使开发者能以面向对象的方式进行数据持久化操作而无需直接编写 SQL 语句。本文提供 Hibernate 入门指南,介绍核心概念及示例代码,涵盖依赖引入、配置文件设置、实体类定义、工具类构建及基本 CRUD 操作。通过学习,你将掌握使用 Hibernate 简化数据持久化的技巧,为实际项目应用打下基础。
        30 0
        |
        11天前
        |
        Java 前端开发 Spring
        技术融合新潮流!Vaadin携手Spring Boot、React、Angular,引领Web开发变革,你准备好了吗?
        【8月更文挑战第31天】本文探讨了Vaadin与Spring Boot、React及Angular等主流技术栈的最佳融合实践。Vaadin作为现代Java Web框架,与其他技术栈结合能更好地满足复杂应用需求。文中通过示例代码展示了如何在Spring Boot项目中集成Vaadin,以及如何在Vaadin项目中使用React和Angular组件,充分发挥各技术栈的优势,提升开发效率和用户体验。开发者可根据具体需求选择合适的技术组合。
        24 0
        |
        14天前
        |
        缓存 Java 数据库连接
        Spring Boot 资源文件属性配置,紧跟技术热点,为你的应用注入灵动活力!
        【8月更文挑战第29天】在Spring Boot开发中,资源文件属性配置至关重要,它让开发者能灵活定制应用行为而不改动代码,极大提升了可维护性和扩展性。Spring Boot支持多种配置文件类型,如`application.properties`和`application.yml`,分别位于项目的resources目录下。`.properties`文件采用键值对形式,而`yml`文件则具有更清晰的层次结构,适合复杂配置。此外,Spring Boot还支持占位符引用和其他外部来源的属性值,便于不同环境下覆盖默认配置。通过合理配置,应用能快速适应各种环境与需求变化。
        26 0
        |
        14天前
        |
        XML Java 决策智能
        拥抱智能决策新纪元!Spring Boot携手LiteFlow规则引擎,让复杂业务处理如丝般顺滑,引领技术潮流!
        【8月更文挑战第29天】LiteFlow是一款专为Java应用设计的轻量级规则引擎,支持条件、循环、分支等多种规则类型,具有组件化设计和高度可扩展性。通过自定义规则和事件监听,它可以显著提升代码的可维护性和可重用性。本文将详细介绍如何在Spring Boot项目中整合LiteFlow,并通过实际案例演示其强大功能。主要步骤包括:添加依赖、配置参数、定义组件及流程,并通过API触发执行。借助LiteFlow,复杂业务逻辑处理变得更加灵活高效。
        34 0
        |
        14天前
        |
        监控 安全 Java
        【开发者必备】Spring Boot中自定义注解与处理器的神奇魔力:一键解锁代码新高度!
        【8月更文挑战第29天】本文介绍如何在Spring Boot中利用自定义注解与处理器增强应用功能。通过定义如`@CustomProcessor`注解并结合`BeanPostProcessor`实现特定逻辑处理,如业务逻辑封装、配置管理及元数据分析等,从而提升代码整洁度与可维护性。文章详细展示了从注解定义、处理器编写到实际应用的具体步骤,并提供了实战案例,帮助开发者更好地理解和运用这一强大特性,以实现代码的高效组织与优化。
        28 0
        |
        14天前
        |
        Java 开发者 Spring
        Spring Boot大法好:解耦、隔离、异步,让代码‘活’起来,性能飙升的秘密武器!
        【8月更文挑战第29天】解耦、隔离与异步是Spring Boot中的关键设计原则,能大幅提升软件的可维护性、扩展性和性能。本文通过示例代码详细探讨了这些原则的应用:依赖注入和面向接口编程实现解耦;模块化设计与配置文件实现隔离;`@Async`注解和`CompletableFuture`实现异步处理。综合运用这些原则,可以显著提升软件质量和性能,使系统更加健壮、灵活和高效。
        21 0
        |
        20天前
        |
        Java BI API
        SpringBoot + POI-TL:高效生成Word报表的技术盛宴
        【8月更文挑战第22天】在日常的工作与学习中,文档处理特别是Word报表的自动生成,是许多项目中不可或缺的一环。传统的手工编辑Word文档不仅效率低下,还容易出错,特别是在需要批量生成包含动态数据的报告时,更是令人头疼不已。幸运的是,结合Spring Boot的简洁高效与POI-TL的强大功能,我们能够轻松实现Word报表的快速生成,既提升了工作效率,又保证了数据的准确性和报表的美观性。
        95 0
        |
        23天前
        |
        SQL 前端开发 NoSQL
        SpringBoot+Vue 实现图片验证码功能需求
        这篇文章介绍了如何在SpringBoot+Vue项目中实现图片验证码功能,包括后端生成与校验验证码的方法以及前端展示验证码的实现步骤。
        SpringBoot+Vue 实现图片验证码功能需求
        |
        22天前
        |
        JavaScript
        SpringBoot+Vue+ElementUI 实现视频播放 轮播图效果
        这篇文章介绍了如何在SpringBoot+Vue+ElementUI项目中使用vue-awesome-swiper插件实现视频播放轮播图效果,包括安装插件、引入项目和使用案例的步骤。
        SpringBoot+Vue+ElementUI 实现视频播放 轮播图效果