SSE

SSE (Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,又称”事件流“ (Event Stream)

它基于HTTP协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送

SSE VS Socket

SSE (Server-Sent Events) 和WebSocket都是实现服务器向客户端实时推送数据的技术

它们在某些方面还是有一定的区别,有各自的优缺点,适用于不同的场景和需求

技术实现

  • SSE基于HTTP 协议,利用了其长连接特性,通过浏览器向服务器发送一个HTTP请求,建立一条持久化的连接
  • WebSocket通过特殊的升级协议(HTTP/1.1 Upgrade或HTTP/2)建立新的TCP连接,与传统HTTP连接不同

数据格式

  • SSE可以传输文本和二进制格式的数据,但只支持单向数据流,即只能由服务器向客户端推送数据
  • WebSocket支持双向数据流,客户端和服务器可以互相发送消息,且没有消息大小限制

连接状态

  • SSE的连接状态仅有三种:已连接、连接中、已断开,连接状态是由浏览器自动维护的,客户端无法手动关闭或重新打开连接
  • WebSocket连接的状态更灵活,可以手动打开、关闭、重连等

兼容性

  • SSE是标准的Web API,可以在大部分现代浏览器和移动设备上使用,但如果需要兼容老版本的浏览器(如 IE6/7/8),则需要使用polyfill库进行兼容
  • WebSocket在一些老版本Android手机上可能存在兼容性问题,需要使用一些特殊的API进行处理

安全性

  • SSE的实现比较简单,都是基于HTTP协议,与普通的Web应用没有太大差异,因此风险相对较低
  • WebSocket需要通过额外的安全措施(如 SSL/TLS 加密)来确保数据传输的安全性,避免被窃听和篡改,否则可能会带来安全隐患

适用场景

  • 如果只需要服务器向客户端单向推送数据,并且应用在前端的浏览器环境中,则SSE是一个更加轻量级、易于实现和维护的选择
  • 如果需要双向传输数据、支持自定义协议、或者在更加复杂的网络环境中应用,则WebSocket更加适合

应用举例

  • ChatGPT返回的数据就使用了SSE技术
  • 实时数据大屏,如果只是需要展示实时的数据可以选用SSE技术,并非一定要使用WebSocket

使用SSH

EventSource对象是HTML5新增的一个客户端API,用于通过服务器推送实时更新的数据和通知

在使用EventSource对象时,可以通过以下方法进行配置和操作:

API

在使用EventSource实例对象时,如果服务器没有正确地设置响应头信息,如Content-Type: text/event-stream,可能会导致EventSource对象无法接收到服务器发送的数据

EventSource()构造函数

EventSource的构造函数接收一个URL参数,通过该URL可以建立起与服务器的连接,并开始接收服务器发送的数据

1
const eventSource = new EventSource(url, options)
  • url: String类型,表示与服务器建立连接的 URL,必填
  • options: Object类型,表示可选参数,常用的可选参数包括
    • withCredentials: Boolean类型,表示是否允许发送Cookie和HTTP认证信息,默认为 false
    • headers: Object类型,表示要发送的请求头信息
    • retryInterval: Number类型,表示与服务器失去连接后,重新连接的时间间隔,默认为1000毫秒

EventSource.readyState属性

readyState属性表示当前EventSource对象的状态,只读,值有以下几个:

  • CONNECTING: 表示正在和服务器建立连接
  • OPEN: 表示已经建立连接,正在接收服务器发送的数据
  • CLOSED: 表示连接已经被关闭,无法再接收服务器发送的数据
1
2
3
4
5
6
7
if (eventSource.readyState === EventSource.CONNECTING) {
console.log('正在连接服务器...')
} else if (eventSource.readyState === EventSource.OPEN) {
console.log('已经连接上服务器!')
} else if (eventSource.readyState === EventSource.CLOSED) {
console.log('连接已经关闭。')
}

EventSource.close()方法

close()方法用于关闭EventSource对象与服务器的连接,停止接收服务器发送的数据

EventSource.onopen事件

onopen事件表示EventSource对象已经和服务器建立了连接,并开始接收来自服务器的数据

当EventSource对象建立连接时,触发该事件

1
2
3
eventSource.onopen = function(event) {
console.log('连接成功!', event)
}

EventSource.onerror事件

onerror事件表示在建立连接或接收服务器数据时发生了错误

当出现错误时,触发该事件

1
2
3
eventSource.onerror = function(event) {
console.log('发生错误:', event)
}

EventSource.onmessage事件

onmessage事件表示已经接收到服务器发送的数据

当接收到数据时,触发该事件

1
2
3
eventSource.onmessage = function(event) {
console.log('接收到数据:', event)
}

代码

后端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import express from 'express'
const app = express()

app.get('/api/sse', (req, res) => {
res.writeHead(200, {
// 需要设置这个请求头后就会变成SSE请求
'Content-Type': 'text/event-stream',
'Connection': 'close'
})

// 获取文件的数据
const data = fs.readFileSync('./index.txt', 'utf8')
// 将数据分割成一个字一个字的数组
const arr = txt.split('')

// 当前需要给出的字坐标
let current = 0

// mock SSE的持续数据
let time = setInterval(() => {
// 如果所有字都给完了,就结束
if (current >= arr.length) {
console.log('end')
clearInterval(time)
return
}
// 返回自定义事件名;可以定义多个事件;如果没有设置,则事件名默认为message
res.write(`event:lmao\n`)
// 以流形式返回数据
res.write(`data:${arr[current]}\n\n`)
current++
// 设置超时
}, 300)
})
app.listen(3000, () => {
console.log('Listening on port 3000')
})

前端:

1
2
3
4
5
6
7
8
9
10
11
12
13
const msg = document.getElementById('message')

// 创建SSE请求
const sse = new EventSource('http://localhost:3000/api/sse' )

sse.addEventListener('open', (e) => {
console.log(e.target)
})

// 对应后端的自定义事件名lmao
sse.addEventListener('lmao', (e) => {
msg.innerHTML += e.data
})