在现代Web应用中,实时数据推送已成为提升用户体验的关键技术之一。本文将深入探讨Server-Sent Events (SSE) 技术,从原理到实践,带你全面掌握如何使用SSE实现前后端实时通信。
Server-Sent Events (SSE) 是一种基于HTTP的服务器向客户端推送数据的技术。与WebSocket不同,SSE是单向的,仅支持服务器向客户端推送数据,但实现简单,且天然支持断线重连。
特性 | SSE | WebSocket |
---|---|---|
通信方向 | 单向(服务器→客户端) | 双向(全双工) |
协议 | HTTP | 独立协议(ws/wss) |
数据格式 | 文本 | 二进制或文本 |
复杂度 | 简单 | 较复杂 |
浏览器支持 | 广泛 | 广泛 |
适用场景 | 实时通知、日志流等单向场景 | 实时聊天、游戏等双向交互场景 |
EventSource
API或自定义fetch请求连接到服务器SSE端点data:
开头虽然问题主要关注前端实现,但了解后端如何实现SSE有助于更好地理解整个工作流程。以下是Node.js Express框架下的简单SSE实现示例:
// 后端SSE实现示例 (Node.js + Express)
app.post('/api/tool/ai/stream-dialogue', (req, res) => {
// 设置SSE所需的响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // 立即发送响应头
// 模拟AI处理过程,分批次发送数据
const sendData = (type, content) => {
res.write(`data: ${JSON.stringify({ type, content })}\n\n`);
};
// 模拟思考过程
sendData('think', '正在分析您的问题...');
// 模拟逐步生成回答
const answerParts = ['这是第一部分回答', '这是第二部分回答', '这是最后一部分回答'];
answerParts.forEach((part, index) => {
setTimeout(() => {
sendData('text', part);
if (index === answerParts.length - 1) {
// 最后推荐相关内容
setTimeout(() => {
sendData('recommended', '您可能还对以下内容感兴趣...');
// 结束连接
res.write('data: {"type":"complete"}\n\n');
res.end();
}, 500);
}
}, 1000 * (index + 1));
});
});
最简单的SSE前端实现方式是使用浏览器原生提供的EventSource
API:
// 使用EventSource API实现SSE客户端
export function dialogueStreamSendWithEventSource(params) {
const { prompt, onMessage, onReasoning, onRecommended, onError, onClose } = params;
// 创建EventSource连接
const eventSource = new EventSource(`/api/tool/ai/stream-dialogue?prompt=${encodeURIComponent(JSON.stringify(prompt))}`);
// 监听消息事件
eventSource.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data);
if (data.type == 'think') {
onReasoning(data.content);
} else if (data.type == 'text') {
onMessage(data.content);
} else if (data.type == 'recommended') {
onRecommended(data.content);
}
} catch (err) {
onError(err);
}
});
// 监听错误事件
eventSource.addEventListener('error', (event) => {
if (eventEvent.readyState === EventSource.CLOSED) {
onClose && onClose();
} else {
onError && onError(new Error('SSE连接错误'));
}
});
// 返回关闭方法
return {
close: () => {
eventSource.close();
onClose && onClose();
}
};
}
注意:原生EventSource
API有一些限制,如只能使用GET请求,无法自定义请求头等,这可能不满足所有场景需求。
对于更复杂的场景,如需要POST请求或自定义请求头,可以使用fetch API手动实现SSE客户端,正如问题中提供的代码示例:
export function dialogueStreamSend(params) {
const { prompt, onMessage, onReasoning, onRecommended, onError, onClose } = params;
// 创建AbortController实例
const controller = new AbortController();
const signal = controller.signal;
const fetchStream = async () => {
try {
const response = await fetch('/api/tool/ai/stream-dialogue', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(prompt),
signal // 将signal传给fetch
});
if (!response.ok) {
throw new Error(`HTTP错误! 状态码: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
while (true) {
const { done, value } = await reader.read();
if (done) {
if (onClose) onClose();
break;
}
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
let jsonData = '';
lines.forEach(line => {
if (line.includes('type')) {
jsonData = line.replace(/^data:/, "").trim(); // 去掉 "data: " 前缀并修剪空格
// 如果jsonData为空,跳过解析
if (!jsonData) {
return;
}
const data = JSON.parse(jsonData)
if(data.type == 'think'){
onReasoning(data.content)
}
if(data.type == 'text'){
onMessage(data.content)
}
if(data.type == 'recommended'){
onRecommended(data.content)
}
}
});
}
} catch (err) {
if(err.name != 'AbortError'){
onError(err)
}
}
};
// 开始请求
fetchStream();
// 返回一个对象,提供close方法用于中止请求
return {
close: () => {
controller.abort();
if (onClose) onClose();
}
};
}
下面展示如何在Vue组件中集成上述SSE功能:
<template>
<div class="chat-container">
<div class="messages">
<div v-for="(message, index) in messages" :key="index"
:class="['message', message.type]">
{{ message.content }}
</div>
</div>
<div v-if="loading" class="loading">AI正在思考...</div>
<div v-if="error" class="error">{{ error.message }}</div>
<button @click="startDialogue" :disabled="loading">开始对话</button>
<button @click="stopDialogue" :disabled="!loading">停止对话</button>
</div>
</template>
<script>
import { dialogueStreamSend } from '@/api/sse';
export default {
data() {
return {
messages: [],
loading: false,
error: null,
streamController: null
};
},
methods: {
async startDialogue() {
this.messages = [];
this.loading = true;
this.error = null;
// 准备对话提示
const prompt = {
// 这里放置你的对话提示数据
message: "请解释SSE技术原理",
context: "用户对实时通信技术感兴趣"
};
// 启动SSE流
this.streamController = dialogueStreamSend({
prompt,
onMessage: (content) => {
this.messages.push({ type: 'response', content });
},
onReasoning: (content) => {
this.messages.push({ type: 'thinking', content });
},
onRecommended: (content) => {
this.messages.push({ type: 'recommendation', content });
},
onError: (err) => {
this.error = err;
this.loading = false;
},
onClose: () => {
this.loading = false;
}
});
},
stopDialogue() {
if (this.streamController) {
this.streamController.close();
this.streamController = null;
}
}
},
beforeUnmount() {
// 组件销毁前确保关闭连接
this.stopDialogue();
}
};
</script>
<style scoped>
.chat-container {
max-width: 600px;
margin: 0 auto;
}
.messages {
height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 10px;
padding: 8px;
border-radius: 4px;
}
.response {
background-color: #f0f0f0;
}
.thinking {
background-color: #e6f7ff;
font-style: italic;
}
.recommendation {
background-color: #f6ffed;
border-left: 3px solid #52c41a;
}
.loading, .error {
padding: 10px;
margin-bottom: 10px;
}
.error {
color: red;
}
</style>
虽然SSE内置了自动重连功能,但在某些情况下可能需要自定义重连逻辑:
export function dialogueStreamSendWithRetry(params, maxRetries = 3) {
let retryCount = 0;
const attemptConnection = () => {
const controller = new AbortController();
const signal = controller.signal;
const fetchStream = async () => {
try {
// ...原有fetch代码...
} catch (err) {
if (err.name != 'AbortError') {
if (retryCount < maxRetries) {
retryCount++;
console.log(`连接失败,尝试第${retryCount}次重连...`);
setTimeout(attemptConnection, 2000 * retryCount); // 指数退避
} else {
onError(err);
}
}
}
};
fetchStream();
return {
close: () => {
controller.abort();
if (onClose) onClose();
}
};
};
return attemptConnection();
}
SSE提供了一种简单有效的服务器向客户端推送数据的方式,特别适合实时通知、日志流、AI交互等场景。相比WebSocket,它更轻量级,实现更简单,且天然支持断线重连。
本文详细介绍了SSE的原理、工作流程,并提供了前后端实现示例,特别是在Vue组件中的集成方式。通过合理使用SSE,可以显著提升Web应用的实时性和用户体验。
在实际项目中,根据具体需求选择合适的技术方案:对于简单的单向实时数据推送,SSE是理想选择;对于需要双向通信的复杂交互场景,WebSocket可能更合适。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。