SSE介绍

  • 基于标准的 HTTP:SSE 建立在标准的 HTTP 协议之上,因此不需要额外的插件或协议。客户端通过普通的 HTTP 连接就可以接收来自服务器的推送消息。
  • 单向通信: SSE 是一种单向通信协议,只允许服务器向客户端推送数据,而客户端不能向服务器发送数据。这使得它非常适合用于服务器向客户端发送实时更新或事件通知。
  • 文本格式: SSE 使用文本格式进行数据传输,因此易于理解和调试。传输的数据以事件流的形式发送,每条消息由一个或多个字段组成,包括事件标识、数据和可选的注释。
  • 自动重连机制: SSE 包含自动重连机制,当连接断开时,客户端会自动尝试重新连接服务器,从而保持通信的持续性。

SSE是一个轻量级协议,相对简单;WebSocket是一种相对较重的协议,相对复杂。但SSE只支持单向交互(服务器给客户发送),Websocket支持双向交互。

数据格式方面, SSE 使用的是 UTF8 编码的文本格式。(这个点很多教程都不说)

SSE的HTTP response 里header Content-Type 的值是 text/event-stream。不允许改变!

结论

在 Web 开发中,SSE 常用于实时更新网页内容、实时通知、实时数据推送等场景。在基于 Java 的 Web 应用中,Spring Framework 提供了对 SSE 的良好支持,可以使用 SseEmitter 或者 WebFlux 中的 Server-Sent Event 类来实现服务器端的 SSE 功能。

SSE的数据格式

每个SSE的消息响应分为4个元素:

  • retry:重试时间,单位毫秒,只能为数字(SSE请求失败,就会发送新的请求)
  • id:消息ID(自定义)
  • event:时间类型(自定义)
  • data:消息的内容(自定义)

下图是4个消息,注意,多个消息之间中间会有个空行(\n\n)。单个消息之间元素间隔是换行\n

retry: 5000
id: 1cd7bb64-4341-4f5d-a690-4298b8a8ae20
event: eventType
data: Sun Nov 20 18:23:11 CST 2022

retry: 5000
id: 2f57295d-3eaa-4e5c-a787-55fff58d9b05
event: eventType
data: Sun Nov 20 18:23:12 CST 2022

retry: 5000
id: 6a9618de-99e7-4c03-91f8-dcdd7601a8d0
event: eventType
data: Sun Nov 20 18:23:13 CST 2022

retry: 5000
id: c5fdcc90-b1f7-4058-9a3a-d63881ffea8b
event: eventType
data: Sun Nov 20 18:23:14 CST 2022

SpringBoot接入SSE

不依赖于任何Jar包(这里前提是你的项目支持MVC哈,99%的项目是不需要的)

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.9</version> <!-- 请替换为您需要的版本号 -->
</dependency>

本质只是一个Http协议,然后指定ContentType,然后返回相应的格式!

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE链接测试</title>
</head>
<body>
消息类型是message消息:
<div id="ssediv">默认消息</div>
<br>
消息类型是diyEventType消息:
<div id="diyssediv">DIY SSE消息</div>
</body>
<script>
    var sse = new EventSource("http://localhost:8081/sse");

    /**
     * 默认是没有指定eventTtpe的消息,但eventType就为message。
     * 等价于addEventListener("message" ...
     */
    sse.onmessage = function (ev) {
        console.error("这里只能处理eventType为message的消息")
        var elementById = document.getElementById("ssediv");
        elementById.innerHTML = ev.data;
    }

    /**
     * 添加指定类型消息处理,eventType是后台自定义的
     */
     sse.addEventListener("diyEventType",event => {
         console.error("自定义事件"+event.data)
         var elementById2 = document.getElementById("diyssediv");
         elementById2.innerHTML = event.data;
     })

    /**
     * SSE连接异常
     */
    sse.onerror = function (){
        alert("服务器已停止!")
    }

    /**
     * SSE连接成功
     */
    sse.onopen = function (){
        alert("服务器已连接!")
    }

    // 不要忘记关闭断开连接哦
    // sse.close()
</script>
</html>

后端代码

@Controller
public class SSE {

    @GetMapping(value = "/sse")
    @ResponseBody
    public String getMessage(HttpServletResponse response) {
        // System.out.println("请求进入了");
        response.setContentType("text/event-stream");   // 指定ContentType,不可变
        response.setCharacterEncoding("utf-8");         // 指定响应字符集,是否可变,没测试,但建议指定utf-8
        while (true) {
            String s = "";
            s += "retry: 5000\n";                   // 客户端没有获取到数据,就不断发送新的请求(间隔5秒),直到有数据。
            s += "id: " + UUID.randomUUID() + "\n"; // 这里指定消息ID
            s += "event: eventType\n";              // 这里定义事件类型,自定义!
            s += "data: " + new Date() + "\n\n";    // 这里设置返回的数据
            try {
                PrintWriter pw = response.getWriter();
                Thread.sleep(1000L);
                pw.write(s);
                pw.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

测试(请关注network样式 ,只有Chrome才能看到,safari、firefox都不支持)

注意避免跨域,浏览器 URL 用 localhost,前端 SSE 的 URl 不能是127.0.0.1。

注意一点:sse的消息实际上只会有一个网络请求。消息藏在EventStream中。每条消息会在此EventStream中是一个记录。下文是客户端接受到服务端多条消息推送的示例。

如果Jmeter直接打到请求上,200个连接,直接导致任何请求无法进行。停止Jmeter后,依旧无法进行请求。lsof -i:服务端口,都是closed也无法

请看第二篇 SSE

更加贴近业务操作的代码

特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取全部资料 ❤