server-sent events(SSE)的基础使用

848 阅读6分钟

一、Server-Sent Events的介绍

Server-Sent Events服务器发送事件,简称SSE。服务器主动给客户端发送消息,常见的有websocket、sse也是其中的一种。

SSE是HTML5规范的一部分,该规范主要由两部分组成,第一部分是服务端与客户端的通讯协议http协议,第二部分是浏览器可供JavaScript使用的EventSource对象。

严格来说http协议是无法做到服务器主动推送消息给客户端的,但是有一种变通的方法,就是服务器向客户端声明,接下来要推送的数据是流式数据。此刻浏览器就明白了这个数据并非是一次性的数据,而是一个数据流,会连续不断的发送过来。

SSE就是利用这种机制,使用流信息向浏览器发生信息,注意目前IE浏览器不支持。

二、SSE和WebSocket对比

SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息

  • websocket是全双工通道,可以双向通信,sse是单向通道,只能服务器向浏览器推送信息。
  • sse使用http协议,现在的服务器软件都支持,websocket是一个独立的协议
  • sse属于轻量级,使用简单,websocket相对复杂
  • sse内置断线重连和消息追踪功能,websocket需要自己实现
  • sse支持自定义发送的消息类型
  • sse一般只用于传送文本,二进制数据需要编码后传送,websocket默认支持二进制数据传送

三、SSE使用实例

1、服务器的实现

这里以nodejs服务器为例实现推送数据流,实际上还是http那一套,只需要把数据设置为流式数据,在通过PassThrough实例对象向客户端推送数据即可。

// 利用express框架搭建服务器
const express = require('express');
const app = express();
const router = express.Router();
const cors = require('cors')
const port = 3000;
// ‌PassThrough流‌是Node.js中的一个流类型,属于双工流(duplex stream),
// 既可以读取数据,也可以写入数据。PassThrough流的主要特点是它不会对数据进行任何更改,
// 只是简单地将从可读流传递来的数据传输到可写流,通常用于需要将数据从一个流传递到另一个流的情况下,而无需对数据进行额外的处理‌
const { PassThrough } = require("stream")

// 解决跨域问题
app.use(cors())

router.get("/api/sendMsg", (req,res)=>{
    // 设置流式数据
     res.set({
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive"
    })
    // 中断连接,浏览器一般默认3秒回进行重连
    // 也可以通过retry设置浏览器重新发起连接的时间间隔
    // 两种情况会导致浏览器重新发起连接:一种是时间间隔到期,二是由于网络错误等原因,导致连接出错
    res.write("retry: 10000\n");
    // 
    const stream = new PassThrough();
    res.body = stream
    stream.pipe(res);
    // 这里用定时器模拟写入数据,向客户端传递数字1到10
    let i=1;
    let timer = setInterval(() => {
        if(i>10){
            // 结束的时候发送stop表示此次请求的服务端数据推送完毕
            stream.write(`data: stop\n\n`);
            clearInterval(timer);
            // 结束流时调用end方法 确保所有的数据都被写入目标流,并且释放相关资源
            stream.end();
        }else{
            // 写入数据格式:data: content\n\n
            // data是客户端接收数据的键名,content是发送客户端的内容,`\n\n` 表示一条消息发送结尾。
            // 写入数据是复杂数据类型的时候通过通过JSON.stringify方法转换为字符串再传递
            stream.write(`data: ${i}\n\n`);
            i++;
        }
    }, 500);
})

app.use(router)

app.listen(port ,()=>{
console.log(`服务器启动成功:http://localhost:${port}`);
})

2、客户端的实现

客户端使用HTML+JavaScript实现接口的请求和数据的接收

<body>
    <button>请求接口</button>
    <div class="result"></div>
    <script>
        const btn = document.querySelector('button')
        const result = document.querySelector('.result')
        btn.addEventListener('click', () => {
            // 实例化EventSource对象,并传入请求接口地址 向服务器发起连接
            const evtSource = new EventSource('http://localhost:3000/api/sendMsg')
            // 监听message事件并接收数据
            evtSource.onmessage = (e) => {
                console.log("接收的数据:",e.data)
                // 判断数据是否已结束,结束需要关闭连接
                if(e.data == "stop"){
                  return evtSource.close()
                }
                // 保存并渲染数据
                result.innerHTML += e.data
            }
            // 错误处理
            evtSource.onerror = (e) => {
                console.log("错误信息:",e)
            }
        })
    </script>
</body>

3、效果演示

sse效果演示.gif

sse效果network演示.gif

四、其他说明

1、EventSource 对象

SSE 的客户端 API 部署在EventSource对象上。下面的代码可以检测浏览器是否支持 SSE

if ('EventSource' in window) {
  // ...
}

2、readyState属性

EventSource实例化对象有readyState属性,用于表示当前的连接状态,该属性只读,取值如下:

  • 0:相当于常量EventSource.CONNECTING,表示连接还未建立,或者断线正在重连。
  • 1:相当于常量EventSource.OPEN,表示连接已建立,可以接收数据。
  • 2:相当于常量EventSource.CLOSED,表示连接已关闭,且不会重连。

3、事件

sse建立连接开始默认会触发以下三种事件:

  1. open:表示连接一旦建立,就会触发open事件。
  2. message:客户端收到服务器推送的数据,就会触发message事件,通过事件对象event.data接收数据。
  3. error:如果发生通信错误或连接中断,就会触发error事件

使用方法:

const source = new EventSource(url)
// 方式一:通过on事件名
source.on事件名 = function(event){
    console.log(event.data)
}

// 方式二:通过addEventListener监听
source.addEventListener("事件名", (event)=>{ 
    console.log(event.data)
})

4、自定义事件

默认情况下,服务器发送的数据,会自动触发浏览器EventSource实例的message实际,开发者还可以自定义sse事件,这种情况下发生回来的数据不会触发message事件,而是触发自定义事件

4-1、服务端自定义事件

以上面的nodejs案例为例,只需要在推送数据之前写入自定义事件名即可

const stream = new PassThrough();
stream.write(`event: msg\n`) // 自定义事件名 msg
stream.write(`data: ${i}\n\n`);

4-2、客户端接收

服务端设置了自定义事件后,客户端必须使用addEventListener来监听自定义事件才生效。

const evtSource = new EventSource('http://localhost:3000/api/sendMsg')
evtSource.addEventListener("msg", function(e){
     //自定义事件必须通过addEventListener来监听并接收数据
     console.log(e.data)
})

5、客户端向服务端发送数据

客户端向服务端发送数据直接按照正常的http协议get请求进行查询参数传递即可。

5-1、客户端

let msg = "你好"
const evtSource = new EventSource('http://localhost:3000/api/sendMsg?msg='+encodeURIComponent(msg))

5-2、服务端

router.get("/api/sendMsg", (req,res)=>{
 // 接收前端传入数据 msg
 const { msg } = req.query;
})

五、使用场景

SSE在大模型AI聊天助手中可以使用到该技术,以流式数据的方式展示内容,提高用户体验,效果如下图:

see聊天助手中的应用.gif