关于sse、websocket与流式渲染

一、SSE是什么?

网络中的 SSE (Server-Sent Events) 是一种服务器向浏览器单向推送数据的机制,常用于需要实时更新的数据传输,如新闻推送、股票行情、聊天应用等。


SSE 的特点

  1. 单向通信:服务器向客户端推送数据,但客户端无法直接通过 SSE 发消息给服务器(客户端可通过其他方式如 HTTP 请求与服务器通信)。
  2. 基于 HTTP/1.1:SSE 使用 HTTP 协议传输,和普通 HTTP 请求兼容,不需要 WebSocket 的复杂握手过程。
  3. 自动重连:如果连接中断,SSE 会自动尝试重新连接。
  4. 文本传输: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

上述消息发送了三次推送:

  1. 第一条为事件类型 message,数据为 “Hello, World!”。
  2. 第二条为 JSON 格式的天气数据。
  3. 第三条是中文文本。

服务端响应完整示例

服务器端的响应头必须设置正确的 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 中,服务端的数据格式必须是 纯文本流,并且按照规范使用 dataidevent 等字段。每条消息以两个换行符(\n\n)分隔,确保客户端能正确解析与展示。同时,SSE 的格式简单而有效,适合需要实时数据推送但不需要复杂双向通信的场景。

三、前端如何处理一段一段的数据?

SSE(Server-Sent Events) 中,服务端会将数据以一段段的方式持续推送给客户端。前端需要通过事件监听器处理这些消息,将数据进行逐条接收和展示。以下是如何在前端处理 SSE 分段数据 的详细步骤。


前端处理 SSE 分段数据的步骤

  1. 使用 EventSource 创建 SSE 连接
  2. 监听不同类型的事件(默认 message 事件或自定义事件)。
  3. 累积或逐步更新数据(如拼接消息、实时更新 UI)。
  4. 错误处理:保证连接断开时能够重连。

基本代码示例

// 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 实时渲染步骤

  1. 接收服务端的推送数据:使用 EventSource 监听不同事件。
  2. 更新 UI:接收数据后,立即更新页面元素(如消息列表)。
  3. 优化性能:对于频繁更新的情况,可以使用节流虚拟列表技术,保证性能不受影响。

基础实现:实时渲染数据

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...');
};

效果:

  1. 每当服务器推送新消息时,onmessage 会捕获并调用 renderMessage() 方法,将消息插入页面。
  2. 自动滚动到页面底部:当消息增加超出容器高度时,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-windowvirtual-scroller)或自己实现。


总结

通过上述代码,你可以实现基于 SSE 的实时数据渲染:

  • 使用 EventSource 持续接收服务端推送的数据。
  • 动态更新 DOM,让页面实时显示最新内容。
  • 使用 自动滚动 让用户始终看到最新消息。
  • 针对高频数据,可以使用 节流虚拟列表 优化性能。

这种方式非常适合聊天应用、实时新闻推送或日志监控系统的前端实现。

五、event.data

onmessage 是如何工作的?

  1. 长连接EventSource 使用 HTTP 长连接,服务器在连接建立后不断推送数据给客户端,而无需客户端频繁发起新的请求。
  2. 实时监听onmessage 事件会在服务器推送 每一条新消息 时被触发,将当前这条消息的数据传递给客户端。
  3. 增量推送:每次推送的数据独立存在,并不会覆盖之前的数据,而是逐条接收。

工作机制示例

服务端推送(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 数据流的特点

  1. 数据流EventSource实时监听数据流变化,服务器每次发送新数据时,客户端会立即捕获。
  2. 自动重连:如果连接中断(如网络波动),EventSource 会尝试自动重连,并从最后的消息 ID 开始接收。
  3. 顺序保证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 是一种非常适合实时数据推送(如聊天、日志、监控等)的轻量级方案。

上一篇:txt数据转为pdf格式并使用base64解密输出


下一篇:国际中文教育知识图谱问答