一、什么是 SSE?
SSE(Server-Sent Events)是一种服务器向客户端推送数据的技术,它允许服务器主动向客户端发送数据,而不需要客户端频繁轮询。SSE 特别适合实时通信场景,比如AI 聊天的流式输出、实时通知、股票行情更新等。
服务器发送事件(SSE)技术,包括其单向通信、基于 HTTP、自动重连等特点。详细阐述了两种前端实现方式:原生 EventSource API 和 fetch 配合 ReadableStream。通过 Express 后端与前端交互的实战案例,展示了如何在 AI 聊天场景中实现流式输出。文章对比了 SSE 与 WebSocket 的区别,分析了优缺点及适用场景,并提供了错误处理、性能优化等代码建议,帮助开发者构建实时 Web 应用。
SSE(Server-Sent Events)是一种服务器向客户端推送数据的技术,它允许服务器主动向客户端发送数据,而不需要客户端频繁轮询。SSE 特别适合实时通信场景,比如AI 聊天的流式输出、实时通知、股票行情更新等。
客户端通过向服务器发送一个 HTTP 请求来建立 SSE 连接。服务器返回一个特殊的响应,设置 Content-Type: text/event-stream 头,告诉客户端这是一个 SSE 流。
服务器以流的形式持续发送数据,每个数据块都是一个 SSE 格式的消息。SSE 消息格式如下:
data: 消息内容
其中:
客户端接收并解析流式数据,根据消息内容进行相应处理。在浏览器中,可以使用 EventSource API 或 fetch + ReadableStream 来处理 SSE。
EventSource 是浏览器内置的 SSE 客户端 API,使用非常简单:
const sse = new EventSource('/api/stream');
sse.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
console.log('收到数据:', data);
});
sse.addEventListener('error', (event) => {
console.error('SSE 错误:', event);
});
注意:EventSource 只支持 GET 请求,无法发送 POST 数据。
当需要向服务器发送 POST 数据时(比如发送用户输入到 AI 模型),可以使用 fetch + ReadableStream 来模拟 SSE:
const response = await fetch('/api/stream-chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ userInput: inputText })
});
if (!response.ok) throw new Error(`HTTP 错误:${response.status}`);
if (!response.body) throw new Error("响应体不可用");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const dataStr = line.slice(6);
const data = JSON.parse(dataStr);
// 处理数据...
}
}
app.post('/api/stream-chat', async (req, res) => {
try {
const { userInput } = req.body;
if (!userInput) return res.status(400).json({ error: "用户输入不能为空" });
// 设置 SSE 响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 立即发送响应头
res.flushHeaders();
// 调用 AI 模型生成回复
const stream = await model.stream(`用户提问:${userInput},请用简洁的语言回复`);
// 逐块发送 AI 输出
for await (const chunk of stream) {
res.write(`data: ${JSON.stringify({ content: chunk?.content || chunk })}\n\n`);
}
// 发送结束标识
res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
res.end();
} catch (err) {
console.error('Error in stream-chat:', err);
res.write(`data: ${JSON.stringify({ error: err?.message || '服务器内部错误' })}\n\n`);
res.end();
}
});
// 发送请求
const response = await fetch('http://localhost:8000/api/stream-chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ userInput: inputText })
});
// 检查响应
if (!response.ok) throw new Error(`HTTP 错误:${response.status}`);
if (!response.body) throw new Error("响应体不可用");
// 创建读取器和解码器
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
// 处理流式数据
while (isStreaming) {
const { done, value } = await reader.read();
if (done) break;
// 解码并处理 SSE 格式数据
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n\n');
buffer = lines.pop() || '';
// 处理每一条消息
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const dataStr = line.slice(6);
const data = JSON.parse(dataStr);
// 处理错误信息
if (data.error) {
aiMsgElement.textContent = `错误:${data.error}`;
isStreaming = false;
break;
}
// 处理结束标识
if (data.done) {
isStreaming = false;
break;
}
// 逐字显示 AI 回复
aiMsgElement.textContent += data.content;
}
}
SSE 使用简单的文本格式,每个消息以 data: 开头,以 \n\n 结束。前端需要:
使用 ReadableStream API 读取流式数据:
使用 TextDecoder API 将二进制数据转换为字符串:
需要处理多种错误情况:
需要管理流式处理的状态:
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(服务器→客户端) | 双向 |
| 协议 | HTTP | WebSocket |
| 实现复杂度 | 低 | 高 |
| 自动重连 | 支持 | 需手动实现 |
| 数据格式 | 文本(通常 JSON) | 二进制或文本 |
| 跨域支持 | 支持(CORS) | 需特殊配置 |
| 适用场景 | 实时通知、流式输出 | 实时聊天、游戏 |
SSE 特别适合以下场景:
try {
// 现有代码...
} catch (error) {
// 显示错误信息
aiMsgElement.textContent += `\n(出错:${error.message})`;
// 在控制台输出错误
console.error("流式接收错误:", error);
// 重置状态
isStreaming = false;
sendBtn.disabled = false;
stopBtn.style.display = 'none';
}
// 对于大型消息,使用 DocumentFragment 减少 DOM 操作
const fragment = document.createDocumentFragment();
const tempElement = document.createElement('div');
// 处理数据时先更新临时元素
tempElement.textContent += data.content;
// 定期更新 DOM(比如每 100ms)
if (Date.now() - lastUpdateTime > 100) {
aiMsgElement.textContent = tempElement.textContent;
lastUpdateTime = Date.now();
}
// 添加加载动画
aiMsgElement.innerHTML = '<div class="loading">生成中...</div>';
// 流式结束后移除加载动画
if (data.done) {
aiMsgElement.innerHTML = aiMsgElement.textContent;
isStreaming = false;
break;
}
SSE 是一种简单高效的服务器向客户端推送数据的技术,特别适合实时流式输出场景。通过本文的介绍,你应该已经了解了:
SSE 虽然简单,但功能强大,是实时 Web 应用的重要工具之一。在实际开发中,根据具体需求选择合适的实时通信方案,才能达到最佳效果。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online