Server-Sent Events (SSE) 技术详解

SSE (Server-Sent Events) 是一种基于 HTTP 的服务器推送技术。它允许服务器通过一个长期的 HTTP 连接,源源不断地向客户端发送文本数据流。

1. 核心概念

通俗地说,SSE 就像是收音机广播。

  • 客户端:打开收音机(建立连接)。
  • 服务器:不断发送广播信号(推送数据)。
  • 单向性:你只能听广播,不能通过收音机向电台说话(只能服务器发给客户端)。

工作流程

  1. 客户端发起一个普通的 HTTP 请求。
  2. 服务器响应头中设置 Content-Type: text/event-stream。
  3. 连接保持打开状态(Keep-Alive)。
  4. 服务器随时可以通过这个连接发送数据。

2. SSE vs WebSocket

这是面试和架构选型中最常见的问题。

特性SSE (Server-Sent Events)WebSocket
通信方向单向 (Server -> Client)双向 (全双工)
协议基于 HTTP 协议独立的 WebSocket 协议 (TCP)
数据格式仅支持 文本 (UTF-8)支持 文本 和 二进制
自动重连原生支持 (浏览器自动处理)不支持 (需手写心跳重连逻辑)
使用难度简单 (像普通 HTTP 请求)较复杂 (需专门的握手和协议处理)
典型应用股票行情、新闻推送、ChatGPT 回复在线聊天室、多人游戏、协作编辑

3. 实战代码示例

A. 后端实现 (Node.js/Express)

SSE 对数据格式有严格要求,每条消息必须以 data: 开头,以 \n\n 结尾。

text
123456789101112131415161718192021222324252627282930313233
const express = require('express');
const app = express();

app.get('/stream', (req, res) => {
  // 1. 设置关键 Header
  res.writeHead(200, {
    'Content-Type': 'text/event-stream', // 声明流式数据
    'Cache-Control': 'no-cache',         // 禁止缓存
    'Connection': 'keep-alive',          // 保持长连接
    'Access-Control-Allow-Origin': '*'   // 允许跨域
  });

  // 2. 发送初始消息
  res.write('data: 连接成功!\n\n');

  // 3. 模拟每隔 1 秒推送一次数据
  const intervalId = setInterval(() => {
    const message = {
      time: new Date().toLocaleTimeString(),
      value: Math.random()
    };
    // 注意格式:data: <JSON字符串>\n\n
    res.write(`data: ${JSON.stringify(message)}\n\n`);
  }, 1000);

  // 4. 当客户端断开连接时,清理定时器
  req.on('close', () => {
    clearInterval(intervalId);
    console.log('客户端已断开');
  });
});

app.listen(3000, () => console.log('SSE Server running on port 3000'));

B. 前端实现 (原生 EventSource API)

浏览器提供了原生的 EventSource 对象来处理 SSE。

text
1234567891011121314151617181920
// 1. 建立连接
const eventSource = new EventSource('http://localhost:3000/stream');

// 2. 监听消息
eventSource.onmessage = function(event) {
  const data = JSON.parse(event.data);
  console.log('收到服务器数据:', data);
  
  // 更新 UI
  document.getElementById('result').innerText = data.time;
};

// 3. 监听错误 (包括连接中断)
eventSource.onerror = function(err) {
  console.error('连接出错:', err);
  // 注意:EventSource 默认会自动尝试重连,无需手动干预
};

// 4. 手动关闭连接
// eventSource.close();

4. SSE 的高级特性

除了基础的 data 字段,SSE 协议还支持其他字段来实现高级功能。特别要注意自动重连与数据补全的区别。

text
1234
id: 101           <-- 消息 ID (关键:用于断线重连恢复)
event: update     <-- 自定义事件类型
retry: 5000       <-- 告诉浏览器如果断连,5秒后重试
data: hello world <-- 数据内容

自动重连与断点续传机制详解

  1. 自动重连 (无需代码): 当连接中断(网络波动或服务器重启)时,浏览器会自动在约 3 秒后尝试重新连接。这是浏览器的默认行为,开发者无需编写任何前端代码。可以通过服务器发送 retry: 5000 字段来修改这个重试间隔。
  2. 数据补全 (需要服务端配合): 仅仅自动重连并不保证能收到“剩余的数据”。 为了不丢失断开期间的数据,必须配合 ID 机制:
    • 服务端:发送每条消息时必须带上 id: 序号。
    • 浏览器:重连时,会自动在新的 HTTP 请求头中带上 Last-Event-ID: 序号(告诉服务器我上次收到了哪)。
    • 服务端逻辑:必须读取这个 Header,并根据 ID 从数据库或缓存中把断开期间错过的消息补发给客户端。如果服务端不处理这个 Header,重连后就只能接收最新的数据,中间的数据会丢失。

5. 优缺点总结

优点

  1. 轻量级:不需要像 WebSocket 那样升级协议,不仅对服务器压力小,而且防火墙/代理服务器对 HTTP 更友好。
  2. 自动重连:浏览器原生内置了重连机制,开发成本极低。
  3. 简单:对于只需要服务器推送的场景(如大盘走势),SSE 是最简单的解决方案。

缺点

  1. 单向性:如果客户端也需要频繁向服务器发送数据(如实时游戏),SSE 不合适。
  2. 连接限制:HTTP/1.1 限制浏览器对同一域名的并发连接数(通常是 6 个)。如果打开多个 Tab 页,连接池可能会被占满。(HTTP/2 解决了这个问题)。
  3. 仅支持文本:无法直接传输二进制文件(WebSockets 支持)。

6. 经典案例:ChatGPT

目前最火的 SSE 应用就是 ChatGPT。

当你和 AI 对话时,AI 的回复是一个字一个字蹦出来的。这不是 WebSocket,而是 SSE。 服务器通过 SSE 流,生成一个 token 就推送到前端渲染一个,从而实现了“打字机”效果,让用户感觉响应速度很快,不需要等待整段话生成完毕。