• Springboot集成SSE实现消息推送之单工通信


    前言

    通常在一些web项目中,会涉及到想客户端推送消息,常见的有Ajax轮询、webSocket,本篇文章主要使用Springboot集成SSE实现向客户端持续推送信息。

    SSE简介

    服务发送事件SSE(Sever-Sent Event),就是基于 HTTP 的技术,浏览器向服务器发送一个保持长连接HTTP请求,服务器单向地向客户端以流形式持续传输数据 。这样可以节约网络资源,不需要建立新连接。

    优点

    服务端不需要其他的类库,开发难度较低。
    不用每次建立新连接,延迟较低。
    数据通过简单且广泛使用的HTTP协议而不是专有协议进行同步。

    对重新建立连接和事件ID功能的内置支持。

    对于利用单向通信的应用程序和服务非常有用。

    缺点

    客户端越多连接越多,会占用服务器大量内存和连接数。

    SSE只支持UTF-8编码,不支持二进制数据。

    对最大打开连接数的严格限制可能使事情变得困难,每个浏览器都设置了限制。

    SSE是单向的。

    Springboot集成SSE简约版

    客户端发送请求到服务端,服务端以流的形式不断向客户端推送数据示例,增加帅气值。

    xml依赖:

    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    html代码:

    DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Springboot集成SSE简约版title>
        <script type="text/javascript">
            let source = new EventSource('/get');
            source.onmessage = function (event) {
                console.info(event.data);
                document.getElementById('text').innerText = event.data
            };
        script>
    head>
    <body>
    <div id="text">div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    后端代码:

       @RequestMapping(value = "/get", produces = "text/event-stream;charset=UTF-8")
        public void push(HttpServletResponse response) {
            response.setContentType("text/event-stream");
            response.setCharacterEncoding("utf-8");
            int i = 0;
            while (true) {
                try {
                    Thread.sleep(1000);
                    PrintWriter pw = response.getWriter();
                    //注意返回数据必须以data:开头,"\n\n"结尾
                    pw.write("data:xdm帅气值加" + i + "\n\n");
                    pw.flush();
                    //检测异常时断开连接
                    if (pw.checkError()) {
                        log.error("客户端断开连接");
                        return;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                i++;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    效果:
    在这里插入图片描述

    Springboot集成SSE升级版

    演示SSE的连接建立、接收数据和异常情况监听处理。

    注:若浏览器不兼容在页面引入evensource.js

    <script src=/eventsource-polyfill.js>script>
    
    • 1

    客户端代码:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title> Springboot集成SSE升级版title>
    head>
    <script>
        let source = null;
        const clientId = new Date().getTime();
        if (!!window.EventSource) {
    
            source = new EventSource('/sse/create?clientId=' + clientId);
        	//建立连接
            source.onopen = function (event) {
                setMessageInnerHTML("建立连接" + event);
            }
            //接收数据
            source.onmessage = function (event) {
                setMessageInnerHTML(event.data);
            }
            //错误监听
            source.onerror = function (event) {
                if (event.readyState === EventSource.CLOSED) {
                    setMessageInnerHTML("连接关闭");
                } else {
                    console.log(event);
                }
            }
        } else {
            setMessageInnerHTML("浏览器不支持SSE");
        }
    
        window.onbeforeunload = function () {
            close();
        };
    
        // 关闭
        function close() {
            source.close();
            const httpRequest = new XMLHttpRequest();
            httpRequest.open('GET', '/sse/close/?clientId=' + clientId, true);
            httpRequest.send();
            console.log("close");
        }
    
        // 显示消息
        function setMessageInnerHTML(innerHTML) {
            document.getElementById('text').innerHTML += innerHTML + '
    '
    ; }
    script> <body> <button onclick="close()">关闭连接button> <div id="text">div> body> 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
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    服务端代码:

    private static Map<String, SseEmitter> cache = new ConcurrentHashMap<>();
    
    String clientId;
    int sseId;
    
    @GetMapping("/create")
    public SseEmitter create(@RequestParam(name = "clientId", required = false) String clientId) {
    
        // 设置超时时间,0表示不过期。默认30000毫秒
        //可以在客户端一直断网、直接关闭页面但未提醒后端的情况下,服务端在一定时间等待后自动关闭网络连接
        SseEmitter sseEmitter = new SseEmitter(0L);
        // 是否需要给客户端推送ID
        if (Strings.isBlank(clientId)) {
            clientId = UUID.randomUUID().toString();
        }
    
        this.clientId = clientId;
        cache.put(clientId, sseEmitter);
        log.info("sse连接,当前客户端:{}", clientId);
        return sseEmitter;
    }
    
    @Scheduled(cron = "0/3 * *  * * ? ")
    public void pushMessage() {
        try {
            sseId++;
            SseEmitter sseEmitter = cache.get(clientId);
            sseEmitter.send(
                    SseEmitter
                            .event()
                            .data("帅气值暴增" + sseId)
                            .id("" + sseId)
                            .reconnectTime(3000)
            );
        } catch (Exception e) {
            log.error(e.getMessage());
            sseId--;
        }
    }
    
    @GetMapping("/close")
    public void close(String clientId) {
        SseEmitter sseEmitter = cache.get(clientId);
        if (sseEmitter != null) {
            sseEmitter.complete();
            cache.remove(clientId);
        }
    }
    
    • 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

    方法参数说明:

     SseEmitter
              .event()
              .data("帅气值暴增" + sseId)
              .id("" + sseId)
              .reconnectTime(3000)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    SseEmitter.event()
    用来得到一个记录数据的容器。


    .data("帅气值暴增" + sseId)
    发送给客户端的数据。


    .id("" + sseId)
    记录发送数据的标识,服务端可以通过HttpServletRequest的请求头中拿到这个id,判断是否中间有误漏发数据。


    .reconnectTime(3000)
    定义在网络连接断开后,客户端向后端发起重连的时间间隔(以毫秒为单位)。

    效果:
    在这里插入图片描述
    参考资料:
    https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async-objects

    https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html

  • 相关阅读:
    【echarts入门】:vue项目中应用echarts
    神经网络控制器设计原理,神经网络控制系统设计
    CSS实现文字颜色渐变
    4795: 【PY】【C1】【分支】寄快递
    vulnhub——narak
    RabbitMQ--交换机
    ES父子级关系Join类型的使用
    Java项目:SSM学生选课管理系统
    mysql笔记
    网络中的TCP协议与UDP协议
  • 原文地址:https://blog.csdn.net/qq_35764295/article/details/127867136