认识 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连接路径添加时间戳
相关推荐
2501_915106322 小时前
移动端网页调试实战,iOS WebKit Debug Proxy 的应用与替代方案
android·前端·ios·小程序·uni-app·iphone·webkit
柯南二号3 小时前
【大前端】React Native 调用 Android、iOS 原生能力封装
android·前端·react native
睡美人的小仙女1274 小时前
在 Vue 前端(Vue2/Vue3 通用)载入 JSON 格式的动图
前端·javascript·vue.js
yuanyxh5 小时前
React Native 初体验
前端·react native·react.js
程序视点5 小时前
2025最佳图片无损放大工具推荐:realesrgan-gui评测与下载指南
前端·后端
程序视点6 小时前
2023最新HitPaw免注册版下载:一键去除图片视频水印的终极教程
前端
小只笨笨狗~7 小时前
el-dialog宽度根据内容撑开
前端·vue.js·elementui
weixin_490354347 小时前
Vue设计与实现
前端·javascript·vue.js
烛阴9 小时前
带你用TS彻底搞懂ECS架构模式
前端·javascript·typescript
卓码软件测评9 小时前
【第三方网站运行环境测试:服务器配置(如Nginx/Apache)的WEB安全测试重点】
运维·服务器·前端·网络协议·nginx·web安全·apache