使用Server-Sent Events (SSE)实现流式输出

1,385 阅读5分钟

什么是SSE?

Server-Sent Events (SSE) 是一种允许服务器向客户端实时推送更新的技术。与WebSocket不同,SSE是基于HTTP协议的,它提供了一种简单的方式来实现服务器到客户端的单向通信

为什么需要流式输出?

在AI时代,特别是使用大型语言模型(LLM)构建聊天机器人时,流式输出变得尤为重要:

  1. 用户体验优化:用户可以边生成边看到结果,而不是等待全部内容生成完毕
  2. 响应速度提升:即使内容尚未完全生成,用户也能立即看到部分结果
  3. 成本效益:对于按token计费的AI服务,流式输出可以更合理地分配资源

实现SSE流式输出的完整示例

前端实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LLM Chatbot</title>
</head>
<body>
    <h1>流式输出</h1>
    <div id="messages"></div>
    <script>
        // 创建EventSource连接
        const source = new EventSource('/sse');
        
        // 监听服务器推送的消息
        source.onmessage = function(event) {
            const messages = document.getElementById('messages');
            messages.innerHTML += event.data; // 追加消息到页面中
        }
    </script>
</body>
</html>

内容解析

  1. EventSource 是浏览器提供的用于接收服务器推送事件的 API,它通过简单的 HTTP 连接建立持久通信,允许服务器单向实时地向客户端推送数据(如聊天消息、实时通知等),并自动处理连接断开和重连,使用方式类似事件监听,只需指定服务器端点并监听消息事件即可实现实时数据接收。

后端实现

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

// 提供静态HTML页面
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
})

// SSE路由
app.get('/sse', (req, res) => {
    // 设置SSE所需的响应头
    res.set({
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
    })
    res.flushHeaders();
    
    // 定时推送消息
    setInterval(() => {
        const message = `Current Time is ${new Date().toLocaleTimeString()}`;
        // 按照SSE格式发送数据
        res.write(`data: ${message}\n\n`);
    }, 1000)
})

// 启动服务器
const http = require('http').Server(app);
http.listen(1314, () => {
    console.log('server is running on 1314');
})

内容解析

前提条件:

//初始化一个新的Node.js项目(自动生成package.json)
npm init -y   
//安装Express框架(Node.js最流行的Web框架)
npm i express  老牌的node 框架

要下载后端的依赖

对应的目录为:

image.png

下载完依赖就会有上面红色方框里面的内容

1. Express 基础设置

const express = require('express');
const app = express();
  • express() :创建 Express 应用实例,用于定义路由和中间件。
  • require('express') :Node.js 中导入 Express 模块(CommonJS 语法)。

2. 静态页面路由

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
});
  • app.get() :定义 HTTP GET 请求的路由。
  • res.sendFile() :发送文件作为响应(这里是 index.html)。
  • __dirname:Node.js 全局变量,表示当前脚本所在的目录路径。

3. SSE 路由核心配置

app.get('/sse', (req, res) => {
    res.set({
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
    });
    res.flushHeaders();
    // ...后续推送逻辑
});
关键响应头设置
  • Content-Type: text/event-stream
    必需的头,声明这是一个 SSE 流。
  • Cache-Control: no-cache
    禁止客户端缓存数据,确保实时性。
  • Connection: keep-alive
    保持 HTTP 连接持久化,避免频繁断开。
API
  • res.set() :设置 HTTP 响应头。
  • res.flushHeaders() :立即发送响应头,确保客户端快速建立连接(避免缓冲延迟)。

4. 数据推送逻辑

setInterval(() => {
    const message = `Current Time is ${new Date().toLocaleTimeString()}`;
    res.write(`data: ${message}\n\n`);
}, 1000);
SSE 数据格式
  • res.write() :向流中写入数据(不结束响应)。

  • 数据格式规则

    • 这里每条消息以 data: 开头,结尾必须有两个换行符 \n\n(SSE 协议要求)。

    • 支持多行数据(每行需加 data: 前缀)。

    • 示例扩展格式:

      res.write(`event: update\nid: 123\ndata: ${JSON.stringify({ time: new Date() })}\n\n`);
      
setInterval
  • 定时推送消息(本例每秒一次),模拟实时数据流。

5. 服务器启动

const http = require('http').Server(app);
http.listen(1314, () => {
    console.log('server is running on 1314');
});
  • http.Server(app) :基于 Express 应用创建 HTTP 服务器。
  • server.listen(port, callback) :启动服务器监听指定端口(1314)。

内容观看

现在我们在终端里面输入node index.js(这里你自己输入自己的内容),启动后端服务器。

然后打开浏览器输入localhost:1314/sse(同样这里可以写你自己定义的端口号),就可以看到流式输出的内容了

屏幕录制 2025-06-27 144233.gif

SSE的工作原理

  1. 连接建立:客户端通过EventSource对象与服务器建立持久连接
  2. 消息格式:服务器发送的消息必须遵循特定格式,支持data:id:event:等字段,每条消息必须以两个换行符(\n\n)结尾 ⚠️ 消息可以包含以下几种字段:
    • data::消息内容(可以多行,每行都需要data:前缀)
    • id::事件ID,客户端可以使用这个ID来恢复事件流。
    • event::事件类型
    • retry::重连时间

    总的来说就是每条消息根据你自己的情况前面要加上面的一种头,但是结尾必须加上\n\n

  3. 保持连接:通过设置Connection: keep-alive保持HTTP连接不中断
  4. 实时推送:服务器可以随时向客户端推送新消息

SSE与WebSocket的比较

特性SSEWebSocket
协议HTTP独立协议
通信单向通信全双工通信
方向性服务器到客户端单向全双工双向
实现复杂度简单相对复杂
自动重连支持需要手动实现
数据格式文本二进制或文本
适用场景服务器推送更新实时交互应用

实际应用场景

  1. AI聊天机器人:流式输出LLM生成的响应
  2. 实时通知系统:推送系统通知或警报
  3. 股票行情更新:实时显示股票价格变动
  4. 新闻推送:实时更新新闻内容
  5. 进度报告:长时间操作的任务进度更新

兼容性

image.png

注意事项

  1. 浏览器兼容性:虽然现代浏览器都支持SSE,但在某些旧版本中可能需要polyfill
  2. 连接限制:浏览器对每个域的SSE连接数有限制(通常是6个)
  3. 错误处理:应实现onerror回调来处理连接中断情况
  4. 性能考量:大量并发SSE连接可能会增加服务器负载

结论

SSE提供了一种简单高效的服务器推送技术,特别适合需要实时更新但不需要双向通信的场景。在AI应用日益普及的今天,掌握SSE技术对于构建响应迅速、用户体验良好的应用至关重要。通过本文的示例,您可以快速上手并实现自己的流式输出功能。