服务端推送技术 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


        相关文章
        |
        1天前
        |
        存储 安全 前端开发
        基于springboot技术的美食烹饪互动平台的设计与实现
        基于springboot技术的美食烹饪互动平台的设计与实现
        |
        1天前
        |
        Java 开发工具 Maven
        根据SpringBoot Guides完成进行示例学习(详细步骤)
        根据SpringBoot Guides完成进行示例学习(详细步骤)
        6 1
        |
        1天前
        |
        Java 索引
        vscode + springboot + HTML 搭建服务端(二)
        vscode + springboot + HTML 搭建服务端(二)
        16 1
        |
        1天前
        |
        Java 关系型数据库 MySQL
        一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
        UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
        34 0
        一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
        |
        1天前
        |
        存储 数据可视化 安全
        Java全套智慧校园系统源码springboot+elmentui +Quartz可视化校园管理平台系统源码 建设智慧校园的5大关键技术
        智慧校园指的是以物联网为基础的智慧化的校园工作、学习和生活一体化环境,这个一体化环境以各种应用服务系统为载体,将教学、科研、管理和校园生活进行充分融合。无处不在的网络学习、融合创新的网络科研、透明高效的校务治理、丰富多彩的校园文化、方便周到的校园生活。简而言之,“要做一个安全、稳定、环保、节能的校园。
        47 6
        |
        1天前
        |
        网络协议 Java 物联网
        Spring Boot与Netty打造TCP服务端(解决粘包问题)
        Spring Boot与Netty打造TCP服务端(解决粘包问题)
        36 1
        |
        1天前
        |
        数据库
        Springboot+mybatis-plus逆向工程生成代码器
        Springboot+mybatis-plus逆向工程生成代码器
        |
        1天前
        |
        存储 前端开发 Java
        基于SpringBoot实现文件上传和下载(详细讲解And附完整代码)
        基于SpringBoot实现文件上传和下载(详细讲解And附完整代码)
        |
        1天前
        |
        Kubernetes Cloud Native Devops
        云原生技术落地实现之二KubeSphere DevOps 系统在 Kubernetes 集群上实现springboot项目的自动部署和管理 CI/CD (2/2)
        云原生技术落地实现之二KubeSphere DevOps 系统在 Kubernetes 集群上实现springboot项目的自动部署和管理 CI/CD (2/2)
        60 1
        |
        1天前
        |
        NoSQL Java 数据库
        【三十】springboot项目上高并发解决示例
        【三十】springboot项目上高并发解决示例
        65 1