简介
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'));
优化措施
- SSE 连接异常时,先断开连接
- SSE 连接异常时,自定义重试次数
- 为SSE连接路径添加时间戳