Skip to content

EventSource是基于http协议的推送技术,用于服务器主动向客户端发送数据,而不需要客服端主动请求

1. 优缺点

优点:

  1. 基于http1.1,目前浏览器都支持
  2. 自动断线重连,无需另写心跳检测
  3. 支持自定义消息类型

缺点:

  1. 单向通讯,客户端无法向服务端发送数据
  2. 仅支持纯文本,二进制数据需转码才能传送

相比于Websocket,EventSource更加轻量级,而且Websocket是独立协议,需要服务端支持 除了不能主动发送数据,可以说Websocket能做的,EventSource都能做

2. 应用场景

  • ChatGPT
  • 天气预报
  • 股票外汇等价格牌
  • 火车公交位置牌

3. 建立连接

EventSource

EventSource使用简单,目前流行浏览器均支持

本文用node来写服务端的请求response

js
const express = require('express');
const cors = require('cors');

const app = express();
const port = 3001;

app.use(cors());

app.listen(port, () => {
  console.log(`Server started on port ${port}`);
});

app.get('/get', (req, res) => {
  res.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  res.flushHeaders();

  let resp = () => {
    const data = {
      message: new Date().toLocaleTimeString()
    };
    res.write(`data: ${JSON.stringify(data)}\n\n`);

    const t = Math.random() * 1000 * 10

    setTimeout(() => {
      resp()
    }, t)
  };
  resp()
});

代码比较简单,使用express框架,只需把Content-Type属性设为text/event-stream即可

随机每隔1-10秒输出当前时间来模拟间歇返回

前端页面的请求,只需要new EventSource(url)对象,url参数是必须的

js
const source = new EventSource('http://localhost:3001/get');

支持第二个参数withCredentials,主要用于跨域请求是否带上cookie

js
const source = new EventSource(url, { withCredentials: true })

完整代码如下

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <div id="box"></div>

    <script>
      const dom = document.getElementById('box');

      const source = new EventSource('http://localhost:3001/get');

      source.onopen = event => {
        console.log('open', source.readyState, event)
      }
      source.onmessage = event => {
        console.log(source.readyState, event)
        const data = JSON.parse(event.data)
        const message = data.message

        dom.innerHTML = `<p>${message}</p>${dom.innerHTML}`
      };
    </script>
  </body>
</html>

运行后,服务端只要返回数据,则会在页面打印出返回的时间

4. 异常处理

EventSource具有断线重连特性,所以网络掉线或是服务端挂了,也会一直重试,通过error事件可以捕获到 type = 'error' 的异常

EventSource

js
source.onerror = event => {
    console.log('error', event)
}

可主动关闭连接,或是通过页面增加按钮来关闭,避免消耗资源(主要是服务端资源)

source.close()

同时可通过只读属性 source.readyState 来判断连接状态

0 = 连接中 1 = 已连接 2 = 已关闭

5. 自定义事件

EventSource

在服务器发送的数据里,type即为事件名称,默认值为 message

type 对应服务端的 event 属性,自定义事件名时,需在服务端赋值,如下

data: 'test'\n event: up\n

表示 up 事件,此时浏览器收到数据流如下

EventSource

前端使用时,需绑定 up 事件

js
source.addEventListener('up', event => {
    console.log('up', event)
})

6. 重连间隔

服务端发给客户端的信息,采用 \n\n 作为每个信息包的分隔,除了上面用到的dataevent,还支持 idretry 等字段

id 为每条消息的标识id

retry为无通讯时客户端主动连接的间隔事件,单位为毫秒

即服务端发送的文本,可以是这样

id: 2221\n data: '这是一段很长的文本'\n retry: 10000\n\n

如果是很长的文本,甚至可以把 data 拆成几行

id: 2221\n data: '这是一段很长的文本'\n data: '文本的第二行'\n data: '文本的第三行'\n retry: 10000\n\n

总之只有识别到 \n\n 才算是一次数据的结束

一般来说,服务端主动发送的间隔,要小于重连间隔,如果超过重连时间(在chrome上测试,默认间隔为5秒),则浏览器会自动请求服务器,并触发异常事件

7. 相关资料

阮一峰《Server-Sent Events 教程》 https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html

江辰《EventSource 引发的一系列事件》 https://juejin.cn/post/7226959605100134455

上次更新于: