大多数开发者都知道WebSocket,但服务器发送事件(SSE)提供了一个更简单、常被忽视的替代方案,值得更多关注。让我们探讨为什么这项技术被低估,以及它如何使你的应用受益。
什么是服务器发送事件?
SSE通过HTTP建立了一个从服务器到客户端的单向通信通道。与WebSocket的双向连接不同,SSE保持一个开放的HTTP连接以进行更新。可以将其视为广播电台:服务器传输,客户端监听。
为什么它被低估?
两个主要因素导致了SSE的低估:
- WebSocket的流行:WebSocket的全双工通信能力掩盖了SSE。
- 感知限制:单向特性可能看起来有限制,但实际上对于许多用例来说已经足够。
SSE的关键优势
实现简单
SSE利用标准的HTTP协议,消除了WebSocket连接的复杂性。
基础设施兼容性
SSE与现有的HTTP基础设施无缝协作:
- 负载均衡器
- 代理
- 防火墙
- 标准HTTP服务器
资源效率
与WebSocket相比,资源消耗更低,因为:
- 单向特性
- 使用标准HTTP连接
- 不需要维护持久的套接字
自动重连
浏览器内置支持:
- 处理连接中断
- 自动重连尝试
- 提供弹性的实时体验
清晰的语义
单向通信模式强制:
- 明确的职责分离
- 简单的数据流
- 简化的应用逻辑
实际应用场景
SSE在以下场景中表现出色:
- 实时新闻流和社交更新
- 股票行情和金融数据
- 进度条和任务监控
- 服务器日志流
- 协作编辑(用于更新)
- 游戏排行榜
- 位置跟踪系统
实现示例
服务器端(Flask)
python
from flask import Flask, Response, stream_with_context
import time
import random
app = Flask(__name__)
def generate_random_data():
while True:
data = f"data: Random value: {random.randint(1, 100)}\n\n"
yield data
time.sleep(1)
@app.route('/stream')
def stream():
return Response(
stream_with_context(generate_random_data()),
mimetype='text/event-stream'
)
if __name__ == '__main__':
app.run(debug=True)
客户端(JavaScript)
python
const eventSource = new EventSource("/stream");
eventSource.onmessage = function(event) {
const dataDiv = document.getElementById("data");
dataDiv.innerHTML += `<p>${event.data}</p>`;
};
eventSource.onerror = function(error) {
console.error("SSE error:", error);
};
代码解释
服务器端组件:
/stream
路由处理SSE连接generate_random_data()
持续生成事件text/event-stream
MIME类型标识SSE协议stream_with_context
维护Flask应用上下文
客户端组件:
EventSource
对象管理SSE连接onmessage
处理器处理传入事件onerror
处理连接问题- 浏览器自动处理重连
限制和注意事项
在实现SSE时,请注意以下限制:
1. 单向通信
- 仅支持服务器到客户端
- 客户端到服务器的通信需要单独的HTTP请求
2. 浏览器支持
- 在现代浏览器中支持良好
- 可能需要为旧浏览器提供polyfill
3. 数据格式
- 主要支持基于文本的数据
- 二进制数据需要编码(例如,Base64)
4. 最好与HTTP/2配合使用
根据MDN文档:
警告:当不使用HTTP/2时,SSE受到最大打开连接数的限制,这在打开多个标签时尤其痛苦,因为每个浏览器的限制非常低(6)。这个问题在Chrome和Firefox中被标记为"不会修复"。这个限制是每个浏览器+域名的,这意味着你可以为www.example1.com打开6个SSE连接,为www.example2.com打开另外6个SSE连接(根据Stack Overflow)。当使用HTTP/2时,最大同时HTTP流的数量由服务器和客户端协商(默认为100)。
最佳实践
- 错误处理
python
eventSource.onerror = function(error) {
if (eventSource.readyState === EventSource.CLOSED) {
console.log("Connection was closed");
}
};
- 连接管理
python
// 清理连接
function closeConnection() {
eventSource.close();
}
- 重连策略
python
let retryAttempts = 0;
const maxRetries = 5;
eventSource.onclose = function() {
if (retryAttempts < maxRetries) {
setTimeout(() => {
// 重连逻辑
retryAttempts++;
}, 1000 * retryAttempts);
}
};
实际示例:ChatGPT的实现
现代模型(LLMs)使用服务器发送事件(SSE)来流式传输响应。让我们探讨这些实现的工作原理及其独特之处。
通用模式
所有主要的LLM提供商都使用以下通用模式实现流式传输:
- 返回
content-type: text/event-stream
标头 - 流式传输数据块,块之间用
\r\n\r\n
分隔 - 每个块包含一行
data: JSON
重要说明
虽然SSE通常与浏览器的EventSource API一起使用,但LLM不能直接使用它,因为:
- EventSource仅支持GET请求
- LLM API需要POST请求
OpenAI实现
基本请求结构
python
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Hello, world?"}],
"stream": true,
"stream_options": {
"include_usage": true
}
}'
响应格式
每个块的结构如下:
python
"data":{
"id":"chatcmpl-AiT7GQk8zzYSC0Q8UT1pzyRzwxBCN",
"object":"chat.completion.chunk",
"created":1735161718,
"model":"gpt-4o-mini-2024-07-18",
"system_fingerprint":"fp_0aa8d3e20b",
"choices":[
{
"index":0,
"delta":{
"content":"!"
},
"logprobs":null,
"finish_reason":null
}
],
"usage":null
}
"data":{
"id":"chatcmpl-AiT7GQk8zzYSC0Q8UT1pzyRzwxBCN",
"object":"chat.completion.chunk",
"created":1735161718,
"model":"gpt-4o-mini-2024-07-18",
"system_fingerprint":"fp_0aa8d3e20b",
"choices":[
{
"index":0,
"delta":{
},
"logprobs":null,
"finish_reason":"stop"
}
],
"usage":null
}
OpenAI返回的关键标头:
python
HTTP/2 200
date: Wed, 25 Dec 2024 21:21:59 GMT
content-type: text/event-stream; charset=utf-8
access-control-expose-headers: X-Request-ID
openai-organization: user-esvzealexvl5nbzmxrismbwf
openai-processing-ms: 100
openai-version: 2020-10-01
x-ratelimit-limit-requests: 10000
x-ratelimit-limit-tokens: 200000
x-ratelimit-remaining-requests: 9999
x-ratelimit-remaining-tokens: 199978
x-ratelimit-reset-requests: 8.64s
x-ratelimit-reset-tokens: 6ms
实现细节
流式完成
流在以下情况下结束:
python
data: [DONE]
使用信息
最终消息包括令牌使用情况:
python
"data":{
"id":"chatcmpl-AiT7GQk8zzYSC0Q8UT1pzyRzwxBCN",
"object":"chat.completion.chunk",
"created":1735161718,
"model":"gpt-4o-mini-2024-07-18",
"system_fingerprint":"fp_0aa8d3e20b",
"choices":[
],
"usage":{
"prompt_tokens":11,
"completion_tokens":18,
"total_tokens":29,
"prompt_tokens_details":{
"cached_tokens":0,
"audio_tokens":0
},
"completion_tokens_details":{
"reasoning_tokens":0,
"audio_tokens":0,
"accepted_prediction_tokens":0,
"rejected_prediction_tokens":0
}
}
}
结论
SSE为实时服务器到客户端通信提供了一个优雅的解决方案。它的简单性、效率以及与现有基础设施的集成使其成为许多应用的绝佳选择。虽然WebSocket在双向通信中仍然有价值,但SSE为单向数据流场景提供了更合适的解决方案。