在 HTTP 协议中,服务器无法向浏览器推送信息,可以使用 WebSocket 来实现两者双向通信。而SSE(Server-Sent Events),在浏览器向服务器请求后,服务器每隔一段时间向客户端发送流数据(是单向的),来实现接收服务器的数据,例如在线视频播放。
一、SSE 是什么
SSE(Server-Sent Events)是一种基于 HTTP 协议的推送技术。服务端可以使用 SSE 来向客户端推送数据,但客户端不能通过SSE向服务端发送数据。
二、应用场景
- 实时通知:在很多情况下,用户期望实时接收到应用的通知,如新消息提醒、商品活动提醒等。
- 节省资源:如果没有服务端推送,客户端需要通过轮询的方式来获取新信息,会造成客户端、服务端的资源损耗。通过服务端推送,客户端只需要在收到通知时做出响应,大大减少了资源的消耗。
- 增强用户体验:通过服务端推送,应用可以针对特定用户或用户群发送有针对性的内容,如优惠活动、个性化推荐等。
- chatGPT
三、与websocket 区别
SSE | websocket | |
---|---|---|
通信 | 单向通信 | 双向通信 |
协议 | HTTP | websocket |
自动重连 | 支持 | 不支持,需要客户端自行支持 |
数据格式 | 文本格式,需要二进制数据需要二外编码 | 默认二进制数据、支持文本 |
浏览器支持 | 大部分主流现代浏览器 | 大部分主流现代浏览器 |
优势:
- 简单易用:基于标准的HTTP协议,易于实现和调试。
- 轻量级:不需要复杂的协议支持,如WebSocket。
- 高效的网络利用:相比轮询,SSE减少了网络流量和延迟。
局限性
- 单向通信:仅支持从服务器到客户端的数据流。
- 浏览器支持:尽管大多数现代浏览器都支持SSE,但存在兼容性问题,尤其是在旧版浏览器中。
- 缺乏控制:相比WebSocket,SSE提供的控制较少,例如无法控制连接的关闭。
四、数据格式
事件流是一个简单的文本流,仅支持 UTF-8 格式的编码。每条消息以一个空行作为分隔符。
在规范中为消息定义了 4 个字段:
event 消息的事件类型。客户端收到消息时,会在当前的 EventSource 对象上触发一个事件,这个事件的名称就是这个字段的值,如果消息没有这个字段,客户端的 EventSource 对象就会触发默认的 message 事件。
id 这条消息的 ID。客户端接收到消息后,会把这个 ID 作为内部属性 Last-Event-ID
,在断开重连 成功后,会把 Last-Event-ID
发送给服务器。
data 消息的数据字段。客户端会把这个字段解析为字符串,如果一条消息有多个 data 字段,客户端会自动用换行符 连接成一个字符串。
retry 指定客户端重连的时间。只接受整数,单位是毫秒。如果这个值不是整数则会被自动忽略。
服务端
1.服务器首先向客户端声明接下来发送的是事件流( text/event-stream )类型的数据,然后就可以向客户端多次发送消息。
服务器端发送的数据的HTTP头信息如下:
css
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
})
数据格式
makefile
field: value\n
field可以取四个值:"data", "event", "id", or "retry",也就是说有四类头信息。每次HTTP通信可以包含这四类头信息中的一类或多类。\n代表换行符。
以冒号开头的行,表示注释。 通常,服务器每隔一段时间就会向浏览器发送一个注释,保持连接不中断。
kotlin
: this is a test stream\n\n
data: some text\n\n
data: another message\n
data: with two lines \n\n
data:数据栏
kotlin
data: begin message\n
data: continue message\n\n
最后一行的data,结尾要用两个换行符号 \n\n,表示数据结束。
EventSource
EventSource 是服务器推送的一个网络事件接口。一个EventSource实例会对HTTP服务开启一个持久化的连接,以text/event-stream 格式发送事件, 会一直保持开启直到被要求关闭。

五、具体实践
服务端实现,基于node
javascript
const http = require('http');
const server = http.createServer((req, res) => {
console.log('req',req.url)
let fileName = '.' +req.url
if(fileName === './sse') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
})
res.write('retry: 10000\n')
res.write('event: connecttime\n')
let interval = setInterval(() => {
res.write(`data: ${new Date().toLocaleTimeString()}\n\n`);
},1000)
// 自定义事件
req.connection.addListener(
'close',
() => {
clearInterval(interval);
},
false
)
}
})
server.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
客户端实现
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="example"></div>
<script >
let source = new EventSource('http://localhost:3000/sse')
let _div = document.getElementById('example')
source.onopen = function (event){
_div.innerHTML ='<p>开始连接...</p>'
}
source.onerror = function (event){
_div.innerHTML ='<p>关闭连接...</p>'
}
source.onmessage = function (event) {
_div.innerHTML += '<p>Ping: ' + event.data + '</p>'
}
</script>
</body>
</html>
输出
