SSE技术的基本理解以及在项目中的使用
文章目录
- 一、什么是 SSE?
- 1. 基本概念
- 2. 核心特点
- 3. 工作原理
- 4. SSE 与 HTTP 的关系
- 5. 与 WebSocket 的比较
- 二、客户端 API -- EventSource
- 1. EventSource
- 2. 基本用法
- 三、我在 Vue3 项目中的使用
- 1. EventSourcePolyfill 与 EventSource 的关系
- 2. 如何使用 EventSourcePolyfill?
- 3. 在项目中使用
最近在写项目时,了解了 SSE 的技术,通过这篇文章,简单的记录一下自己在项目中学习 SSE 的收获。
一、什么是 SSE?
1. 基本概念
Server-Sent Events
(SSE) 是一种基于 HTTP 的服务器到客户端的 单向实时通信技术 ,允许服务器主动向客户端单向推送数据的技术。
2. 核心特点
- 单向通信:仅支持服务器到客户端的单项数据推送、
- 基于 HTTP:使用简单,无需额外协议,兼容现有 HTTP 基础设施。
- 长连接:通过长轮询(long-polling)机制保持连接,减少频繁建立连接的开销。
- 自动重连:连接中断时,客户端会自动尝试重新连接。
- 轻量协议:数据格式简单(纯文本),适合高频底数据量的场景。
3. 工作原理
示例图:
文字描述:
- 客户端发起连接
// url为服务器接口地址
const eventSource = new EventSource('url')// 浏览器向服务器发送一个 HTTP GET 请求,等待服务器响应。
- 服务器建立长连接
服务器响应头需包含:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Connection: keep-alive
Cache-Control: no-Cache# 服务器保持连接开放,准备推送数据
- 服务器推送数据
数据格式规则:
- 每条消息以
data:
开头, 一两个换行符\n\n
结尾。 - 可定义自定义事件(如:
event: update
)
data: Hello!\n\n
event: price\n
data: {"symbol": "BTC", "price": 42000}\n\n
- 客户端接收并处理数据
监听默认的 message
事件或自定义事件(如 price)
eventSource.onmessage = (e) => {/** 处理默认事件 */
}
eventSource.addEventListener('price', (e) => {/** 处理自定义事件 */
})
- 连接终止与重连
客户端或服务器可主动关闭连接:
eventSource.close() // 客户端主动关闭
若连接意外中断(如网络问题),客户端自动重连。
4. SSE 与 HTTP 的关系
- 基于 HTTP/1.1 及以上 :SSE 使用 HTTP 协议,兼容性良好。
- 轻量级协议 :无需复杂握手(如 WebSocket),适合简单实时场景。
- 数据分块传输 :服务器通过 HTTP 分块编码(Chunked Encoding)持续发送数据。
5. 与 WebSocket 的比较
特性 | SSE | WebSocket | 轮询(Polling) |
---|---|---|---|
通信方向 | 单向(服务器->客户端) | 双向 | 单向(客户端主动) |
协议 | HTTP | WebSocket(WS) | HTTP |
连接开销 | 低(长连接) | 中(双向握手) | 高(频繁连接) |
适用场景 | 实时通知、数据监控 | 聊天、游戏、协作 | 低频更新 |
二、客户端 API – EventSource
1. EventSource
在 MDN 中介绍到:EventSource 接口是 web 内容与服务器发送事件通信的接口。一个 EventSource 实例会对 HTTP 服务器开启一个持久化的连接,以 text/event-stream 格式发送事件,此连接会一直保持开启直到通过调用 EventSource.close() 关闭。
可以通过下方代码检测某种浏览器是否有这个对象
if (`EventSource` in window) {// ...
}
使用 SSE 时,浏览器先创建一个 EventSource 实例,向服务器发起连接。
const eventSource = new EventSource(url)
url 是服务器的接口地址,例如:https://example.com/message/sse,如果需要跨域,可以指定第二个参数,打开 withCredentials
属性,表示是否一起发送 Cookie
const eventSource = new EventSource(url, { withCredentials: true })
EventSource 实例的 readyState 属性,表明连接的当前状态。该属性只读,可以取以下值。
- 0:相当于 常量 EventSource.CONNECTING, 表示 连接还未建立,或者断线正在重连。
- 1:相当于常量 EventSource.OPEN,表示连接已经建立,可以接受数据。
- 2:相当于常量 EventSource.CLOSED,表示连接已断,且不会重连。
2. 基本用法
- 连接一旦建立,就会触发 open 事件,可以在 onopen 属性定义回调函数。
// 建立连接
const eventSource = new EventSource(url, { withCredentials: true })eventSource.onopen = (e) => {// ...
}
- 客户端收到服务器发来的数据,就会触发 message 事件,可以在 onmessage 属性的回调函数。
eventSource.onmessage = (e) => {// e.data 就是服务器端传回的数据(文本格式)。const data = e.data// handle message
}
- 如果发生通信错误(比如连接中断),就会触发 error 事件,可以在 onerror 属性定义回调函数。
eventSource.onerror = (e) => {// handle error
}
- close 方法用于关闭 SSE 连接
eventSource.close()
- 自定义事件
默认情况下,服务器发来的数据,总是触发浏览器 EventSource 实例的 message 事件。开发者还可以自定义 SSE 事件,这种情况下,发送回来的数据不会触发 message 事件。
eventCource.addEventListener('price', (e) => {const data = event.data// handle message
})
上面的代码中,浏览器对 SSE 的 price 事件进行监听。注意:事件要前后端进行约定
三、我在 Vue3 项目中的使用
我在做 SSE 模块时,通过创建了一个 messageStore 仓库进行 SSE 相关内容的初始化和消息的处理。除此之外,原生的 EventSource 是无法满足项目中的需求的,因为原生的 EventSource 无法进行自定义请求头等,所以采用了一个第三方库: EventSourcePolyfill。它实现了与原生 EventSource 相同的 API,但扩展了原生 EventSource 的功能(如跨域请求、自定义请求头等),并解决了原生 EventSource 在 IE、旧版浏览器 中的兼容性问题。
1. EventSourcePolyfill 与 EventSource 的关系
特性 | EventSource | EventSourcePolyfill |
---|---|---|
定位 | 浏览器原生对象 | 第三方 Polyfill 库 |
兼容性 | 仅支持现代浏览器(Chrome、Firefox、Safari 等) | 支持旧浏览器(如 IE 10+、旧版 Android/iOS) |
功能扩展 | 仅基础功能 | 支持自定义请求头、跨域凭据、重试策略等 |
依赖关系 | 无需额外引入 | 需通过 npm 或 CDN 引入库 |
API 一致性 | 完全一致 | 完全兼容原生 API,无学习成本 |
2. 如何使用 EventSourcePolyfill?
- 安装库
npm install event-source-Polyfill
- 引入并创建实例(基本与原生的 EventSource 一致)
import { EventSourcePolyfill } from 'event-source-polyfill'// 创建 SSE 连接(支持自定义请求头和跨域凭据)
const eventSource = new EventSourcePolyfill('/sse-endpoint', {headers: {Authorization: 'Bearer xxxx' // 自定义请求头},withCredentials: true, // 跨域时携带 CookieheartbeatTimeout: 30000 // 超时时间(毫秒)
})// 监听消息(与原生 EventSource 完全一致)
eventSource.onmessage = (event) => {console.log('收到数据:', event.data)
}
3. 在项目中使用
import { defineStore } from 'pinia'
import { useUserStore } from '@/store/userStore'
import { EventSourcePolyfill } from 'event-source-polyfill'export const useMessageStore = defineStore('message', {state: () => ({// 存储原始消息列表messages: [],// EventSourcePolyfill实例eventSource: null// ....处理消息的变量}),action: {// 初始化连接initSSE() {// 1. 关闭旧连接(防止重复连接)if (this.eventSource) {this.eventSource.close()}// 2. 需要进行token校验的话可以获取tokenconst userStore = useUserStore()const token = userStore.getToken()// 3. 创建实例this.eventSource = new EventSourcePolyfill(URL, {headers: {Authorization: `Bearer ${token}`,Accept: 'text/event-stream'},withCredentials: true // 携带 cookiesheartbeatTimeout: 60000 // 心跳检测})// 记录重连次数let reconnectAttempts = 0const MAX_RETRIES = 5 // 最大重试次数// 连接成功回调this.eventSource.onopen = (e) => {console.log('SSE 连接成功')// reconnectAttempts = 0; // 重置重试计数器};// 消息处理this.eventSource.onmessage = (event) => {try {this.handleMessage(event)} catch (error) {console.error('消息处理失败:', error)}};// 错误处理(核心修改)this.eventSource.onerror = (error) => {console.error('SSE 连接异常:', error)// 仅在连接完全关闭时重连if (this.eventSource?.readyState === EventSource.CLOSED) {console.log('连接已关闭,尝试重连...')this.handleSSEError(reconnectAttempts, MAX_RETRIES)reconnectAttempts++}};},closeSSE () {if (this.eventSource) {this.eventSource.close();this.eventSource = null;}if (this.reconnectTimer) {clearTimeout(this.reconnectTimer);}},handleMessage () {//....}}
})
以上是我对 SSE 的理解,本人还在学习阶段,能力有限,有什么不对的地方还请指出来。