• Server-Sent Events 一种轻量级的Push方式



    简单来说,Server-Sent Events(简称SSE)是服务端发送事件,即服务端Push的一种机制。

    SSE工作原理

    一般来说HTTP协议是要客户端先请求服务器,服务器才能响应给客户端,无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(event-streaming)。

    也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。

    SSE 就是利用这种机制,使用流信息向客户端推送信息。

    以浏览器和服务器为例,如下所示:

    在这里插入图片描述

    1. 客户端请求建立事件流类型的连接,即Request Headers Accept = text/event-stream。

    在这里插入图片描述

    1. 服务端响应请求,并将Response Headers Content-Type设置为text/event-stream,证明数据将以这种类型传送。
      在这里插入图片描述
    2. 服务端如果有数据就会发送给客户端。

    SSE的特点

    说到服务端推送,自然就想到WebSocket,那相比于WebSocket,SSE有哪些特点呢?

    WebSocketSSE
    全双工,可以同时发送和接收消息单工,只能服务端单向发送消息
    独立的协议基于HTTP协议
    协议相对复杂协议相对简单,易于理解和使用
    默认不支持断线重连默认支持断线重连
    默认支持传送二进制数据一般只用来传送文本,二进制数据需要编码后传送
    不支持自定义发送的数据类型支持自定义发送的数据类型
    支持CORS不支持CORS,协议和端口都必须相同

    SSE的推送数据格式

    • event: 事件类型,服务端可以自定义,默认是message事件

    • Id: 每一条事件流的ID,在失败重传事件流的时候有重要作用

    • retry: 浏览器连接断开之后重连的间隔时间,单位:毫秒,在自动重新连接的过程中,之前收到的最后一个事件流ID会被发送到服务端。

    • data: 发送的数据

    每个字段K-V后面用"\n"结尾,如:
    在这里插入图片描述
    真正的数据用data字段表示,一般放到最后,使用"\n\n"结尾。

    如果数据很长,可以分成多行,最后一行用\n\n结尾,前面行都用\n结尾。

    data: begin message\n
    data: continue message\n\n

    如:

    data: {\n
    data: "name": "xujian",\n
    data: "age", 18\n
    data: }\n\n
    
    • 1
    • 2
    • 3
    • 4

    SSE的使用

    客户端

    对于大部分浏览器都通过EventSource API 支持了SSE。

    1、判断你的浏览器是否支持SSE:

    if(typeof(EventSource)!=="undefined")
    {
        alert('支持')
    }
    else
    {
        alert('不支持')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2、如果支持,编写测试页面:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
    head>
    <body>
    
    body>
    <script>
    var username = 'xujian';
    // 创建EventSource对象,同时建立连接
    <!--服务端使用SseEmitter时使用-->
    var source = new EventSource('http://localhost:8080/sseEmitter/connect/' + username);
    <!--服务端不使用SseEmitter时使用-->
    <!--var source = new EventSource('http://localhost:8080/sseEmitter/data');-->
    
    document.write(username + '正在连接...
    '
    ); // 监听连接建立完成事件 source.addEventListener('open', function (e) { document.write(username + '连接成功~
    '
    ); }, false); // 监听连接错误事件 source.addEventListener('error', function (e) { document.write(username + '连接错误!
    '
    ); }); // 监听自定义消息推送事件,事件名称为“psh”,这个名字由服务端设置 source.addEventListener('psh', function (e) { document.write('收到消息:' + e.data + '
    '
    ); });
    script> html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    当然EventSource API也有对Java的支持。可以参考:https://github.com/launchdarkly/okhttp-eventsource

    服务端

    如果使用了Springboot,其内置了SseEmitter封装了对SSE的支持。

    package com.example.springbootdemo.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    @RequestMapping("/sseEmitter")
    public class SseEmitterController {
        private static final Map<String, SseEmitter> emitterMap = new HashMap<>();
    
        /**
         * 服务端不使用SseEmitter时使用
         *
         * @param response
         * @throws IOException
         */
        @GetMapping(value = "/data")
        public void getData(HttpServletResponse response) throws IOException {
            response.setContentType("text/event-stream;charset=UTF-8");
            response.getWriter().write("retry: 5000\n");
            response.getWriter().write("data: hahahaha\n\n");
            response.getWriter().flush();
            System.in.read();
        }
    
        /**
         * 服务端使用SseEmitter时使用
         *
         * @param username
         * @return
         * @throws IOException
         */
        @GetMapping(value = "/connect/{username}", produces = "text/event-stream;charset=UTF-8")
        public SseEmitter connect(@PathVariable String username) throws IOException {
            SseEmitter sseEmitter = new SseEmitter(0L);
            sseEmitter.onCompletion(() -> {
                System.out.println(username + "连接结束!");
                emitterMap.remove(username);
            });
            sseEmitter.onError((t) -> {
                System.out.println(username + "连接出错!错误信息:" + t.getMessage());
                emitterMap.remove(username);
            });
            sseEmitter.onTimeout(() -> {
                System.out.println(username + "连接超时!");
                emitterMap.remove(username);
            });
            emitterMap.put(username, sseEmitter);
    
            sseEmitter.send("连接建立成功");
            return sseEmitter;
        }
    
        /**
         * 服务端使用SseEmitter时使用
         *
         * @param username
         * @return
         * @throws IOException
         */
        @GetMapping(value = "/send/{username}")
        public String send(@PathVariable String username) throws IOException {
            SseEmitter sseEmitter = emitterMap.get(username);
            if (sseEmitter == null) {
                return "没查询到该用户的连接!";
            }
            sseEmitter.send(SseEmitter.event().name("psh").data("Hello~"));
            return "发送成功~";
        }
    
        /**
         * 服务端使用SseEmitter时使用
         *
         * @return
         * @throws IOException
         */
        @GetMapping(value = "/sendAll")
        public String sendAll() throws IOException {
            for (SseEmitter sseEmitter : emitterMap.values()) {
                sseEmitter.send(SseEmitter.event().name("psh").data("Hello~"));
            }
            return "发送完成~";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    如果不使用SseEmitter,那就需要自己按照固定格式构造SSE的响应数据。

    效果展示

    1)浏览器输入:http://localhost:8080/test.html
    在这里插入图片描述
    2)推送数据:http://localhost:8080/sseEmitter/send/xujian

    3)浏览器接收到数据:
    在这里插入图片描述


    相关代码请参考:https://gitee.com/xujian01/blogcode/tree/master/springcode/src/main/java/com/jarry/sse

  • 相关阅读:
    2023年上半年软考网工选择题易错总结
    CSS之linear-gradient( ) 函数—背景颜色渐变设计
    麒麟v10 安装jenkins
    SQL Server大分区表没有空分区的情况下如何扩展分区的方法
    Java基础知识点面试专题
    [极客大挑战 2019]RCE ME 1
    【Unity入门计划】Collision2D类&Collider2D类
    HTML如何制作音乐网站(如何搭建个人音乐网页)
    K8S LoadBalancer kube-vip 部署
    ICCV 2023|PViC:构建交互谓词视觉上下文,高效提升HOI Transformer检测性能
  • 原文地址:https://blog.csdn.net/qq_18515155/article/details/126085205