• home > theory > CST > network >

    EventSource:Server-Sent Events协议学习笔记

    Author:zhoulujun Date:

    SSE 协议本质上是一个客户端发起的 HTTP Get 请求,服务器在接到该请求后,返回 200 OK 状态,同时附带以下 HeadersContent-Type:

    SSE 是一种仅使用 HTTP就可以实现客户端跟服务端建立单向长连接,从而实现服务端向客户端发送通知的目的 HTML5 标准。所以服务端基本不需要有啥变化。只需要按照特定的格式返回http请求即可!

    SSE 协议

    本质上是一个客户端发起的 HTTP Get 请求,服务器在接到该请求后,返回 200 OK 状态,同时附带以下 Headers

    • Content-Type: text/event-stream,SSE 的 MIME Type 规定为 text/event-stream

    • Cache-Control: no-cache,SSE 肯定不允许缓存

    • Connection: keep-alive,SSE 是一个一直打开的 TCP 连接,所以 Connection 为 Keep-Alive

    之后,服务器保持连接,在 Body 中持续发送文本流,以实现实时消息推送。

    由于是常规http GET请求,所以url传参数、基于cookie的身份认证都没有变化,只是需要发送Accept: text/event-stream来告诉服务端这是一个eventsource请求,服务端则要返回Content-Type: text/event-stream来确认对SSE的支持。


    基础格式

    文本流基础格式如下,以行为单位的,以冒号分割 Field 和 Value,每行结尾为 \n,每行会Trim掉前后空字符,因此 \r\n 也可以。

    field: value\n

    注释以冒号打头,格式如下

    : This is a comment\n

    为了连接保活,服务端可以每隔一段时间给服务端发一个注释,比如:idle

    内容都推荐以utf8编码


    id:事件唯一标示(可选)

    每一个事件可以指定 ID,可以用于标记每个通知消息,这样在建立连接时,客户端可以把上次断开时最后拿到的id发给服务端,以此来获取连续的通知。

    id: msg1\n
    data: message\n\n

    浏览器会一直跟踪最近的事件ID,如果发生了重连,浏览器会把最近接收到的事件ID放入 HTTP Header “Last-Event-ID” 中,作为一种简单的同步机制。

    event:事件(可选)

    事件之间用 额外的\n 隔断, 每个事件既可以为单行,也可为多行。

    下面所示是两个由单行组成的事件

    data:  message\n\n
    data:  message2\n\n

    而这一个是由多行组成的一个事件,更加易读

    data: {\n
    data: "foo": "bar",\n
    data: "baz", 555\n
    data: }\n\n


    命名事件

    除了 ID 唯一标示一个事件之外,也可以通过命名的方式,区分一组类型的事件。默认情况下,事件会被命名为 “message”。

    event: foo\n
    data: a foo event\n\n
    data: an unnamed event\n\n
    data: a bar event\n
    event: bar\n\n

    上面的例子实际上是三个事件,第一个事件命名为 “foo”,第二个事件没有命名,第三个事件命名为”bar”。可以看出,在一个事件内部,”event” 可以放在前面,也可以放在末尾。

    建议命名事件:这便于浏览器的addEventListener()可以只监听自己所以感兴趣的消息类型

    retry:重连时间(可选)

    毫秒数,告诉客户端当连接断开后,等待多久再重新连接。

    一般情况下,连接中断的时候,客户端会在 3 秒内进行重连,这个时间也可以由服务器来指定

    retry: 10000\n


    SSE保活

    在使用 EventSource 的时候,与标准的HTTP请求,有两个关键的不同点:

    • 服务器保持请求开启,而不是在发送完一个完整的响应后立即关闭连接。

    • 响应头 Content-Type 被设置为 text/event-stream,告诉客户端数据是以服务器发送事件的格式收到的。

    为了保持连接,EventSource 遵循几个步骤:

    • 自动重连:如果连接因为任何原因中断,EventSource 客户端会尝试自动重连到服务器。它会等待一段时间(通常是几秒钟),然后尝试再次发起连接。

    • 心跳消息:服务器可以定期发送注释行(以一个冒号 “:” 开头的行)作为心跳,即使没有实际的数据要发送。这样做可以防止某些网络基础设施(如代理或负载均衡器)因为超时而关闭看起来空闲的连接。

    • 自定义重连时间:服务器可以在发送的消息中包含一个 retry 字段来告知客户端在尝试重连前应等待的时间。

    • 最后事件ID:如果连接断开,客户端会发送一个特殊的 HTTP 头 Last-Event-ID,包含上次接收到的事件的ID。这样服务器可以从断点继续发送事件,保证没有事件丢失。

    所有这些机制共同工作,帮助 EventSource 连接保持活跃并且在可能的情况下自动恢复。然而,开发者需要在服务器端实现适当的逻辑来支持这些机制,特别是定时发送心跳消息以及正确处理 Last-Event-ID 头的逻辑。

    SSE工程实践

    检测SSE支持

    一般可以通过检测 EventSource 对象是否存在来判定当前浏览器是否支持 SSE

    function supportsSSE() {
      return !!window.EventSource;
    }

    EventSource


    属性说明
    EventSource.onerror当发生错误时被调用,并且在此对象上派发 error 事件
    EventSource.onmessage服务器端发送给客户端一条消息时触发
    EventSource.onopenSSE 连接刚打开时触发
    EventSource.readyState表示连接状态(CONNECTING 、OPEN 和 CLOSED)
    EventSource.url代表源头的 URL



    SSE使用

    浏览器的eventsource便是对这个技术的原生支持。使用上,

    直接创建 EventSource 对象即可,创建完成后,浏览器会及时打开。下面是案例代码

    var source =new EventSource(url);
    // 连接事件源
    source.onopen = function(event) {
      // handle open event
    };
    source.addEventListener("open", function(event) {
      // handle open event
    }, false);
    
    // 接收事件源
    source.onmessage = function (event) {
      var data = event.data;
      var origin = event.origin;
      var lastEventId = event.lastEventId;
      // handle message
    };
    source.addEventListener('message', function (event) {
      var data = event.data;
      var origin = event.origin;
      var lastEventId = event.lastEventId;
      // handle message
    }, false);
    
    // 错误处理
    source.onerror = function(event) {
      // handle error event
    };
    source.addEventListener("error", function(event) {
      // handle error event
    }, false);
    
    //主动断开连接
    source.close();

    其实看文档就够了:https://developer.mozilla.org/zh-CN/docs/Web/API/EventSource


    连接状态查看

    switch (source.readyState) {
      case EventSource.CONNECTING:
        // do something
        break;
      case EventSource.OPEN:
        // do something
        break;
      case EventSource.CLOSED:
        // do something
        break;
      default:
        // this never happens
        break;
    }


    SSE应用情况

    相较于 WebSocket 而言,Server-Sent Events (简称SSE)更少被人知晓,具体实践也较少。原因有两点:

    • WebSocket 比 SSE 更强大,Websocket 在客户端和服务器之间建立了双向的实时通信。而 SSE 只支持从服务器到客户端的单向实时通信。

    • WebSocket 在浏览器方面支持更广(详见下图),IE / Edge 几乎根本不支持 SSE

    但是SSE简单易用,于只需要能向客户端传输异步服务器消息的 Web 应用程序,服务器发送的事件是一个优雅且简单的解决方案。

    业界中有许多知名的应用案例,特别是在需要实时或近实时信息更新的场景中。

    Facebook 和 Twitter 等社交媒体平台可能会使用类似于SSE的技术来推送通知和更新给用户,比如用户的时间线更新或者是新的消息通知。

    开发和运维工具如Grafana,可以利用SSE来实时更新仪表盘,显示系统的实时运行状态和性能指标。


    参考文章:

    SSE(server-sent events)的原理 https://www.hustyx.com/cpp/371/




    转载本站文章《EventSource:Server-Sent Events协议学习笔记》,
    请注明出处:https://www.zhoulujun.cn/html/theory/ComputerScienceTechnology/network/2024_0126_9033.html