什么是SSE?
Server-Sent Events (SSE) 是一种允许服务器向客户端实时推送更新的技术。与WebSocket不同,SSE是基于HTTP协议的,它提供了一种简单的方式来实现服务器到客户端的单向通信。
为什么需要流式输出?
在AI时代,特别是使用大型语言模型(LLM)构建聊天机器人时,流式输出变得尤为重要:
- 用户体验优化:用户可以边生成边看到结果,而不是等待全部内容生成完毕
- 响应速度提升:即使内容尚未完全生成,用户也能立即看到部分结果
- 成本效益:对于按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>
内容解析:
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 框架要下载后端的依赖
对应的目录为:
下载完依赖就会有上面红色方框里面的内容
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(同样这里可以写你自己定义的端口号),就可以看到流式输出的内容了
SSE的工作原理
- 连接建立:客户端通过
EventSource对象与服务器建立持久连接 - 消息格式:服务器发送的消息必须遵循特定格式,支持
data:、id:、event:等字段,每条消息必须以两个换行符(\n\n)结尾 ⚠️ 消息可以包含以下几种字段:data::消息内容(可以多行,每行都需要data:前缀)id::事件ID,客户端可以使用这个ID来恢复事件流。event::事件类型retry::重连时间
总的来说就是每条消息根据你自己的情况前面要加上面的一种头,但是结尾
必须加上\n\n - 保持连接:通过设置
Connection: keep-alive保持HTTP连接不中断 - 实时推送:服务器可以随时向客户端推送新消息
SSE与WebSocket的比较
| 特性 | SSE | WebSocket |
|---|---|---|
| 协议 | HTTP | 独立协议 |
| 通信 | 单向通信 | 全双工通信 |
| 方向性 | 服务器到客户端单向 | 全双工双向 |
| 实现复杂度 | 简单 | 相对复杂 |
| 自动重连 | 支持 | 需要手动实现 |
| 数据格式 | 文本 | 二进制或文本 |
| 适用场景 | 服务器推送更新 | 实时交互应用 |
实际应用场景
- AI聊天机器人:流式输出LLM生成的响应
- 实时通知系统:推送系统通知或警报
- 股票行情更新:实时显示股票价格变动
- 新闻推送:实时更新新闻内容
- 进度报告:长时间操作的任务进度更新
兼容性
注意事项
- 浏览器兼容性:虽然现代浏览器都支持SSE,但在某些旧版本中可能需要polyfill
- 连接限制:浏览器对每个域的SSE连接数有限制(通常是6个)
- 错误处理:应实现
onerror回调来处理连接中断情况 - 性能考量:大量并发SSE连接可能会增加服务器负载
结论
SSE提供了一种简单高效的服务器推送技术,特别适合需要实时更新但不需要双向通信的场景。在AI应用日益普及的今天,掌握SSE技术对于构建响应迅速、用户体验良好的应用至关重要。通过本文的示例,您可以快速上手并实现自己的流式输出功能。