目录

认识 SSE 消息通信

简介

SSE(Server-Sent Events,服务器发送事件)是一种在 服务器到客户端的单向通信 场景下使用的 Web 技术。它允许服务器向浏览器持续推送消息,而客户端无需频繁轮询服务器。

特点

  • 单向通信:服务器可以主动推送数据到客户端,但客户端不能主动向服务器发送数据(客户端只能通过 HTTP 请求进行交互)。

  • 基于 HTTP 协议:不像 WebSocket 需要专门的协议,SSE 直接基于 HTTP,因此更易与现有的 Web 基础设施(如 Nginx、CDN)兼容。

  • 自动重连 :如果连接断开,浏览器会自动尝试重新连接(可配置 retry 时间)。

  • 纯文本格式 :默认采用 UTF-8 编码的 text/event-stream 格式传输数据。

  • 支持浏览器原生 API:大部分现代浏览器(如 Chrome、Firefox、Edge)都原生支持 SSE。

应用

前端侧(Vue2.js)

1. 封装 SSE 为 sse.js 文件

js 复制代码
// SSE 事件公共枚举
const SSE_EVENTS = {
    OPEN: 'open',
    CLOSE: 'close',
    ERROR: 'error',
    MESSAGE: 'message'
}

// 为接口路径添加时间戳
function addTimeToPath(path = '') {
    if (!path) return `?_t=${Date.now()}`

    if (path.includes('?')) {
        return `${path}&_t=${Date.now()}`
    } else {
        return `${path}?_t=${Date.now()}`
    }
}

// 封装的 SSE 通用工具类:Server-Sent Events
export default class SSE {
    constructor({ base, path, emitter, retryDelay = 3000, retryCount = 3 }) {
        this.sse = null
        this.sseBase = base || 'localhost:3000' // SSE 接口服务
        this.ssePath = path // SSE 接口路径
        this.sseEvents = SSE_EVENTS // SSE 事件枚举

        this.sseRetryDelay = retryDelay // 延迟 N 毫秒后重试
        this.sseRetryCount = retryCount // 失败重连次数
        this.reconnectCounter = 0 // 重连计数器
        this.reconnectTimeout = null // 重连延迟

        if (typeof emitter === 'function') {
          this.sseEmitter = emitter
        } else {
          this.sseEmitter = () => {}
        }

        this.initSSE()
    }

    initSSE() {
        if (!this.ssePath) {
          console.warn('Please Check SSE Path!')
          return
        }

        if (!EventSource) {
          console.warn('Do not support SSE!') // 检查是否支持 SSE,不支持则退出
          return
        }

        this.sse = new EventSource(`${this.sseBase}${addTimeToPath(this.ssePath)}`, {
          withCredentials: true
        })

        // 监听连接
        this.sse.onopen = (e) => {
          this.sseEmitter({ type: SSE_EVENTS.OPEN, e })
        }

       // 监听普通消息事件
       this.sse.addEventListener('message', (e) => {
          try {
            const msgData = JSON.parse(e.data || null) || {}

            this.sseEmitter({ type: SSE_EVENTS.MESSAGE, data: msgData, e })
          } catch (error) {
            console.warn('Error: sse Tools parse msgData error', e, error)
          }
        })

        // 监听错误
        this.sse.onerror = (error) => {
          this.sseEmitter({ type: SSE_EVENTS.ERROR, error })

          this.close() // SSE 异常时,主动关闭 SSE

          if (this.reconnectCounter >= this.sseRetryCount) {
            clearTimeout(this.reconnectTimeout) // 清空延迟器
            this.reconnectCounter = 0 // 清空计数器
            return
          }

          this.reconnectTimeout = setTimeout(() => {
            this.reconnectCounter += 1
            this.initSSE()
          }, this.sseRetryDelay) // 延迟 N 毫秒后重试
        }
    }

    // 监听自定义事件
    bindCustomEvt(eventName = 'heartbeat') {
        if (!eventName) return

        this.sse.addEventListener(eventName, (e) => {
          try {
            const msgData = JSON.parse(e.data || null) || {}

            this.sseEmitter({ type: eventName, data: msgData, e })
          } catch (error) {
            console.warn('Error: sse Tools parse msgData error', e, error)
          }
        })
    }

    close() {
        this.sseEmitter && this.sseEmitter({ type: SSE_EVENTS.CLOSE })
        this.sse && this.sse.close()
        this.sse = null
        clearTimeout(this.reconnectTimeout)
    }
}

2. 将 sse.js 应用到 Vue 页面中

vue 复制代码
<template>
  <div>{{ message }}</div>
</template>

<script>
import SSE from './sse.js'
import { get } from 'lodash-es'

export default {
  name: 'SSEMessagePush',
  data() {
    return {
      message: '' // 消息
    }
  },
  beforeDestroy() {
    this.destroySSE()
  },
  mounted() {
    this.reifySSE()
  },
  methods: {
    // 实例化 SSE 等
    reifySSE() {
      const Route = parseHashToRoute()

      if (Route.name === 'login') {
        this.destroySSE()
        return
      } else if (this.sse) {
        return
      }

      this.sse = new SSE({
        path: `/sse`,
        emitter: ({ type, data }) => {
          if (type === this.sse.sseEvents.MESSAGE) {
             this.distributeMessage(data) // 派发消息
          }
        }
      })
    },
    // 销毁 SSE 等
    destroySSE() {
      if (!this.sse) return

      this.sse.close() // 关闭 SSE
      this.sse = null // 回收 SSE
    },
    // 派发消息
    distributeMessage(data = {}) {
      this.message += data.message
    }
  }
}
</script>

服务端(Node.js)

node.js 复制代码
const express = require('express');
const app = express();

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

let count = 0;
const interval = setInterval(() => {
    count++;
    res.write(`data: Hello SSE! Count: ${count}\n\n`);
}, 1000);

req.on('close', () => {
    clearInterval(interval);
});
});

app.listen(3000, () => console.log('SSE server running on port 3000'));

优化措施

  1. SSE 连接异常时,先断开连接
  2. SSE 连接异常时,自定义重试次数
  3. 为SSE连接路径添加时间戳
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
哟哟耶耶2 小时前
npm-npm init与npm init -y区别与作用(初始化一个新的node.js项目)
前端·npm·node.js
计算机毕设定制辅导-无忧学长7 小时前
HTML 新手入门:从零基础到搭建第一个静态页面(二)
前端·javascript·html
luckyext7 小时前
Postman用JSON格式数据发送POST请求及注意事项
java·前端·后端·测试工具·c#·json·postman
烛阴8 小时前
JavaScript 函数对象与 NFE:你必须知道的秘密武器!
前端·javascript
px52133448 小时前
Solder leakage problems and improvement strategies in electronics manufacturing
java·前端·数据库·pcb工艺
eli9608 小时前
node-ddk,electron 开发组件
前端·javascript·electron·node.js·js
全宝8 小时前
🔥一个有质感的拟态开关
前端·css·weui
老K(郭云开)8 小时前
最新版Chrome浏览器加载ActiveX控件技术--allWebPlugin中间件一键部署浏览器扩展
前端·javascript·chrome·中间件·edge
老K(郭云开)8 小时前
allWebPlugin中间件自动适应Web系统多层iframe嵌套
前端·javascript·chrome·中间件
银之夏雪9 小时前
Vue 3 vs Vue 2:深入解析从性能优化到源码层面的进化
前端·vue.js·性能优化