一、SSE是什么?
网络中的 SSE (Server-Sent Events) 是一种服务器向浏览器单向推送数据的机制,常用于需要实时更新的数据传输,如新闻推送、股票行情、聊天应用等。
SSE 的特点:
- 单向通信:服务器向客户端推送数据,但客户端无法直接通过 SSE 发消息给服务器(客户端可通过其他方式如 HTTP 请求与服务器通信)。
- 基于 HTTP/1.1:SSE 使用 HTTP 协议传输,和普通 HTTP 请求兼容,不需要 WebSocket 的复杂握手过程。
- 自动重连:如果连接中断,SSE 会自动尝试重新连接。
- 文本传输:SSE 主要用于传输文本数据,传输的数据格式通常为 UTF-8。
SSE 的实现示例
1. 服务器端 (Node.js 示例):
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
setInterval(() => {
res.write(`data: ${new Date().toLocaleTimeString()}\n\n`);
}, 1000);
}
}).listen(3000, () => console.log('SSE server running on http://localhost:3000'));
2. 客户端 (HTML + JavaScript 示例):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSE Demo</title>
</head>
<body>
<h1>Server-Sent Events Demo</h1>
<div id="messages"></div>
<script>
const eventSource = new EventSource('/events');
eventSource.onmessage = (event) => {
const messages = document.getElementById('messages');
messages.innerHTML += `<p>${event.data}</p>`;
};
eventSource.onerror = () => {
console.error('Connection lost, retrying...');
};
</script>
</body>
</html>
SSE 与 WebSocket 的对比:
特点 | SSE | WebSocket |
---|---|---|
通信方向 | 单向(服务器 → 客户端) | 双向(服务器 ↔ 客户端) |
协议 | HTTP/1.1 | 自定义 WebSocket 协议 |
数据格式 | 文本(UTF-8) | 文本和二进制 |
适用场景 | 实时推送但不需要双向通信的数据 | 实时交互、需要双向通信的场景 |
连接复杂度 | 简单,基于 HTTP 请求 | 复杂,需要握手过程 |
SSE 的适用场景:
- 新闻推送:如订阅新闻源的实时更新。
- 股票市场数据:实时行情推送。
- 实时通知系统:如 Web 上的通知或警报系统。
- 在线聊天(简单版):单向消息传递,如聊天消息广播。
SSE 提供了一种轻量级解决方案,用于在某些应用场景下替代 WebSocket,特别是当仅需单向数据流时。
二、服务端推送的数据格式是什么样的?
在 Server-Sent Events (SSE) 中,服务器推送的数据格式是基于 文本格式 的特殊结构,每条消息由若干字段组成,每个字段通过换行符(\n
)分隔。服务器将这些数据连续传输给客户端,以下是常用的 SSE 数据格式结构。
SSE 消息格式:
SSE 消息的格式遵循以下结构:
data: <消息数据>
id: <消息ID>
event: <事件类型>
retry: <自动重连时间>
- 每条消息之间需要使用 两个换行符(
\n\n
)分隔。 -
id
字段用于标记消息的 ID,便于客户端追踪和在重连时获取丢失的消息。 -
data
是消息的主要内容,支持多行数据。 -
event
用于标记事件类型(可选),客户端可基于不同事件类型处理消息。 -
retry
指定客户端断开连接后的重连间隔(以毫秒为单位),可选。
示例消息:
data: Hello, World!
id: 1
event: message
retry: 3000
data: {"temperature": 25, "humidity": 60}
id: 2
data: 这是第二条消息
id: 3
上述消息发送了三次推送:
- 第一条为事件类型
message
,数据为 “Hello, World!”。 - 第二条为 JSON 格式的天气数据。
- 第三条是中文文本。
服务端响应完整示例:
服务器端的响应头必须设置正确的 MIME 类型,以确保客户端能够正确解析 SSE 流。
示例响应头:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
示例数据流:
data: Server time: 2024-10-24 14:00:00
id: 101
data: New stock price: AAPL 180.55
id: 102
event: customEvent
data: Custom event message
id: 103
retry: 5000
- 第一条推送显示服务器的时间。
- 第二条推送为股票价格更新。
- 第三条定义了一个自定义事件
customEvent
。 -
retry: 5000
指定客户端在断开连接时应每 5 秒尝试重连。
客户端接收数据的示例:
event.data 是每个数据流的数据,而非完整的
每次服务端发送一条消息时,EventSource 会触发 onmessage 事件,其中包含一条新的数据。
const eventSource = new EventSource('/events');
// 默认消息事件处理
eventSource.onmessage = (event) => {
console.log('Message:', event.data);
};
// 处理自定义事件
eventSource.addEventListener('customEvent', (event) => {
console.log('Custom Event:', event.data);
});
// 错误处理
eventSource.onerror = () => {
console.error('Connection error, trying to reconnect...');
};
总结:
在 SSE 中,服务端的数据格式必须是 纯文本流,并且按照规范使用 data
、id
、event
等字段。每条消息以两个换行符(\n\n
)分隔,确保客户端能正确解析与展示。同时,SSE 的格式简单而有效,适合需要实时数据推送但不需要复杂双向通信的场景。
三、前端如何处理一段一段的数据?
在 SSE(Server-Sent Events) 中,服务端会将数据以一段段的方式持续推送给客户端。前端需要通过事件监听器处理这些消息,将数据进行逐条接收和展示。以下是如何在前端处理 SSE 分段数据 的详细步骤。
前端处理 SSE 分段数据的步骤
-
使用
EventSource
创建 SSE 连接。 -
监听不同类型的事件(默认
message
事件或自定义事件)。 - 累积或逐步更新数据(如拼接消息、实时更新 UI)。
- 错误处理:保证连接断开时能够重连。
基本代码示例
// 1. 创建 SSE 连接
const eventSource = new EventSource('/events');
// 2. 默认消息处理:逐条接收并展示
eventSource.onmessage = (event) => {
console.log('Received message:', event.data);
appendDataToUI(event.data); // 自定义逻辑将数据展示到页面
};
// 3. 监听自定义事件
eventSource.addEventListener('customEvent', (event) => {
console.log('Custom Event:', event.data);
});
// 4. 错误处理:如断开连接时自动尝试重连
eventSource.onerror = (error) => {
console.error('Connection lost, retrying...', error);
};
// 5. 自定义函数:将数据添加到页面
function appendDataToUI(data) {
const messagesDiv = document.getElementById('messages');
const newMessage = document.createElement('p');
newMessage.textContent = data;
messagesDiv.appendChild(newMessage);
}
展示逐条消息
HTML 页面结构:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE Example</title>
</head>
<body>
<h1>SSE Data Stream</h1>
<div id="messages"></div>
<script src="sse-handler.js"></script> <!-- 引入前端逻辑 -->
</body>
</html>
如何处理分段数据
方案 1:逐条处理和展示
每当服务器发送一条消息时,客户端就将它添加到页面中。
eventSource.onmessage = (event) => {
appendDataToUI(event.data); // 实时将数据展示
};
方案 2:拼接消息(累积数据流)
如果服务端的消息是分段发送的,需要客户端在完整接收后进行拼接。例如,假设服务端每次发送数据的一部分:
let accumulatedData = ''; // 用于拼接的全局变量
eventSource.onmessage = (event) => {
accumulatedData += event.data; // 累积数据
if (event.data.endsWith('[END]')) { // 判断是否为完整数据
appendDataToUI(accumulatedData);
accumulatedData = ''; // 重置累积数据
}
};
处理 JSON 数据
如果服务端推送的是 JSON 数据,需要在前端解析后展示:
eventSource.onmessage = (event) => {
try {
const jsonData = JSON.parse(event.data);
console.log('JSON Data:', jsonData);
appendDataToUI(`User: ${jsonData.user}, Message: ${jsonData.message}`);
} catch (error) {
console.error('Invalid JSON:', event.data);
}
};
处理自定义事件的场景
服务端发送不同类型的事件时,可以在前端通过 addEventListener
针对不同事件分别处理:
eventSource.addEventListener('news', (event) => {
console.log('News:', event.data);
appendDataToUI(`News: ${event.data}`);
});
eventSource.addEventListener('alert', (event) => {
console.log('Alert:', event.data);
alert(`Alert: ${event.data}`);
});
总结
- 在 SSE 前端处理 中,核心是通过
EventSource
持续接收服务器推送的消息,并根据需要逐条或累积处理数据。 - 可以监听默认事件(
message
)和自定义事件。 - 拼接数据 时要确保能正确判断数据结束标志,避免数据错乱。
- 通过 JSON 解析 支持复杂数据格式。
SSE 适合实时消息展示和数据推送的场景,例如聊天应用、新闻推送等。这个方案相比 WebSocket 更轻量,适合单向数据流的需求。
四、如何实现实时渲染
要在前端实现实时渲染 SSE 推送的数据,可以将数据一边接收一边更新到页面中。具体实现方式依赖于 HTML 和 JavaScript,通过 DOM 操作 动态将数据插入到页面,并确保界面流畅地显示最新内容。
SSE 实时渲染步骤
-
接收服务端的推送数据:使用
EventSource
监听不同事件。 - 更新 UI:接收数据后,立即更新页面元素(如消息列表)。
- 优化性能:对于频繁更新的情况,可以使用节流或虚拟列表技术,保证性能不受影响。
基础实现:实时渲染数据
HTML 结构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real-time SSE Rendering</title>
<style>
#messages {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ccc;
padding: 10px;
}
p {
margin: 5px 0;
}
</style>
</head>
<body>
<h1>Real-time Messages</h1>
<div id="messages"></div>
<script src="script.js"></script> <!-- 引入 JavaScript -->
</body>
</html>
JavaScript 实现:实时接收和渲染
// 创建 SSE 连接
const eventSource = new EventSource('/events');
// 获取 DOM 元素
const messagesDiv = document.getElementById('messages');
// 处理服务端默认推送的 message 事件
eventSource.onmessage = (event) => {
renderMessage(event.data);
};
// 将消息渲染到页面
function renderMessage(message) {
const newMessage = document.createElement('p');
newMessage.textContent = message;
messagesDiv.appendChild(newMessage);
// 保持滚动条在底部(自动滚动)
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// 错误处理:连接丢失时显示提示
eventSource.onerror = () => {
renderMessage('Connection lost. Reconnecting...');
};
效果:
- 每当服务器推送新消息时,
onmessage
会捕获并调用renderMessage()
方法,将消息插入页面。 -
自动滚动到页面底部:当消息增加超出容器高度时,
scrollTop
确保用户总是看到最新消息。
性能优化:节流处理
如果数据推送频率较高,直接更新 DOM 可能导致性能问题。可以使用节流(throttling),限制每秒的渲染次数。
let lastRenderTime = 0;
function throttledRenderMessage(message) {
const now = Date.now();
if (now - lastRenderTime > 100) { // 限制每100ms渲染一次
renderMessage(message);
lastRenderTime = now;
}
}
eventSource.onmessage = (event) => {
throttledRenderMessage(event.data);
};
优化方式:虚拟列表(Virtual List)
当消息数据量非常大时,可以使用虚拟列表技术,只渲染可见区域内的数据。可以借助第三方库(如 react-window
、virtual-scroller
)或自己实现。
总结
通过上述代码,你可以实现基于 SSE 的实时数据渲染:
- 使用
EventSource
持续接收服务端推送的数据。 - 动态更新 DOM,让页面实时显示最新内容。
- 使用 自动滚动 让用户始终看到最新消息。
- 针对高频数据,可以使用 节流 或 虚拟列表 优化性能。
这种方式非常适合聊天应用、实时新闻推送或日志监控系统的前端实现。
五、event.data
onmessage
是如何工作的?
-
长连接:
EventSource
使用 HTTP 长连接,服务器在连接建立后不断推送数据给客户端,而无需客户端频繁发起新的请求。 -
实时监听:
onmessage
事件会在服务器推送 每一条新消息 时被触发,将当前这条消息的数据传递给客户端。 - 增量推送:每次推送的数据独立存在,并不会覆盖之前的数据,而是逐条接收。
工作机制示例
服务端推送(SSE)数据格式:
data: {"temperature": 25, "unit": "C"}
id: 1
data: {"temperature": 26, "unit": "C"}
id: 2
data: {"message": "System OK"}
id: 3
前端监听新数据:
// 建立 SSE 连接
const eventSource = new EventSource('/events');
// 每当服务器推送一条新数据时,onmessage 就会触发
eventSource.onmessage = (event) => {
console.log('Received new data:', event.data); // 打印当前推送的数据
processData(event.data); // 处理新数据
};
// 自定义数据处理逻辑
function processData(data) {
const parsedData = JSON.parse(data);
console.log('Parsed Data:', parsedData);
// 在页面中展示数据或加入队列等操作
}
逐条处理新数据
每当服务器推送一条新数据时:
-
onmessage
会自动触发,并返回当前这条推送的数据。 - 这是一种流式处理,即:每次推送的新数据都是单独处理的,不会覆盖或与之前的数据混合。
EventSource
数据流的特点
-
数据流:
EventSource
是实时监听数据流变化,服务器每次发送新数据时,客户端会立即捕获。 -
自动重连:如果连接中断(如网络波动),
EventSource
会尝试自动重连,并从最后的消息 ID 开始接收。 -
顺序保证:
EventSource
保证了数据的顺序性,确保客户端按服务端的发送顺序接收数据。
如何判断每条数据是否完整?
在某些情况下,如果数据分片发送(比如需要多个部分组合成一条完整消息),可以使用结束标识(如 [END]
)来判断:
示例:拼接分片数据
let accumulatedData = '';
eventSource.onmessage = (event) => {
accumulatedData += event.data; // 累积数据片段
if (event.data.endsWith('[END]')) {
console.log('Complete Data:', accumulatedData);
processData(accumulatedData);
accumulatedData = ''; // 重置累积数据
}
};
总结
-
onmessage
会在每次服务器推送一条新数据时被触发,event.data
包含这条消息的内容。 - 数据流监听是实时的,客户端可以逐条接收并处理新数据。
- 数据不会被覆盖,而是按服务端发送的顺序逐条接收。
- 可以根据需要对高频数据进行拼接、节流或批量处理,保证页面的性能和用户体验。
EventSource
是一种非常适合实时数据推送(如聊天、日志、监控等)的轻量级方案。