实时通信不只是 WebSocket
提到实时通信,很多人脑子里蹦出来的第一个词就是 WebSocket。
但你知道吗?WebSocket 只是实时通信的其中一种方案。如果你只需要服务器单方面推送消息给客户端,SSE(Server-Sent Events)可能是更好的选择。
SSE 基于标准的 HTTP 协议,不需要特殊的握手过程,浏览器原生支持,而且自带重连机制。对于新闻推送、股票行情、监控告警这类单向通信场景,SSE 比 WebSocket 简洁得多。
SSE 是什么?
SSE 的全称是 Server-Sent Events,是 HTML5 规范的一部分。它允许服务器主动向客户端推送数据,而且完全建立在 HTTP 之上。
你可以把它理解成一个持久化的 HTTP 连接。客户端发起请求后,服务器不关闭连接,而是持续往里面塞数据。客户端收到数据后触发事件回调。
SSE 的核心特点:
- 单向推送:只能服务器发给客户端,客户端用普通 HTTP POST 发消息
- 文本格式:默认传输文本数据(JSON 也可以),不需要序列化
- 自动重连:连接断开后浏览器自动尝试重连
- 事件类型:支持自定义事件名称,客户端按类型处理
- 跨域支持:天然支持 CORS,不像 WebSocket 需要额外配置
前端使用 SSE
前端使用 SSE 非常简单,只需要几行代码:
const eventSource = new EventSource('/api/stream');
eventSource.onopen = function() {
console.log('连接已建立');
};
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
};
// 监听自定义事件
eventSource.addEventListener('update', function(event) {
console.log('更新事件:', event.data);
});
// 错误处理
eventSource.onerror = function(error) {
console.error('连接出错:', error);
};
EventSource 是浏览器内置的 API,Chrome、Firefox、Safari、Edge 全部支持。不需要引入任何第三方库。
后端实现 SSE
Node.js 示例
const express = require('express');
const app = express();
app.get('/api/stream', (req, res) => {
// 设置 SSE 响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // 把响应头先发送出去
let id = 0;
// 每秒推送一条数据
const interval = setInterval(() => {
id++;
const data = JSON.stringify({
id: id,
time: new Date().toISOString(),
message: `第 ${id} 条推送`
});
res.write(`id: ${id}\n`);
res.write(`data: ${data}\n\n`);
}, 1000);
// 客户端断开时清理定时器
req.on('close', () => {
clearInterval(interval);
});
});
app.listen(3000);
关键点有三个:
Content-Type 必须是 text/event-stream。这是 SSE 的协议标识,告诉浏览器这是一个 SSE 连接。
不能缓存。加上 Cache-Control: no-cache 和 Connection: keep-alive,确保连接一直开着。
数据格式要规范。每条消息以 data: 开头,两条消息之间用两个换行符分隔。
Python 示例(FastAPI)
from fastapi import FastAPI
import asyncio
import json
from datetime import datetime
app = FastAPI()
@app.get("/api/stream")
async def stream():
async def event_stream():
counter = 0
while True:
counter += 1
yield f"id: {counter}\n"
yield f"data: {json.dumps({'time': str(datetime.now()), 'count': counter})}\n\n"
await asyncio.sleep(2)
from starlette.responses import StreamingResponse
return StreamingResponse(event_stream(), media_type="text/event-stream")
Go 示例
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
type Message struct {
ID int `json:"id"`
Time time.Time `json:"time"`
Data string `json:"data"`
}
func streamHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
counter := 0
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
counter++
msg := Message{
ID: counter,
Time: time.Now(),
Data: fmt.Sprintf("第 %d 条消息", counter),
}
data, _ := json.Marshal(msg)
fmt.Fprintf(w, "id: %d\ndata: %s\n\n", counter, data)
flusher.Flush()
}
}
func main() {
http.HandleFunc("/api/stream", streamHandler)
http.ListenAndServe(":3000", nil)
}
SSE vs WebSocket 怎么选?
很多人纠结用哪个。其实答案很简单:看需求。
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 服务器推送到客户端 | SSE | 实现简单,自带重连 |
| 双向实时通信 | WebSocket | SSE 不支持客户端主动推送 |
| 高并发推送 | SSE | 基于 HTTP,容易做负载均衡 |
| 游戏实时对战 | WebSocket | 低延迟,双向通信 |
| 聊天室 | WebSocket | 需要互相收发消息 |
| 股票行情推送 | SSE | 单向推送,重连友好 |
| 监控告警通知 | SSE | 简单可靠,运维友好 |
记住一个原则:如果只需要服务器单向推送,优先选 SSE。如果需要双向通信,才考虑 WebSocket。
SSE 的局限性和注意事项
SSE 不是万能的,它有几个明显的限制:
第一,只能文本数据。 SSE 传输的是纯文本,没法传二进制文件。如果你想推送图片或者大文件,得走 WebSocket 或者轮询。
第二,连接数有限制。 浏览器对同一个域名同时建立的 HTTP 连接数量有限制,一般是 6 个。SSE 占用一个连接,如果页面里开了太多 SSE 连接,可能会影响其他 HTTP 请求。
第三,不适合极低延迟场景。 SSE 基于 HTTP 长连接,虽然比轮询快,但延迟还是比 WebSocket 高一些。游戏或者高频交易场景不建议用 SSE。
第四,需要处理连接稳定性。 虽然 SSE 自带重连机制,但中间如果有 Nginx 或者 CDN 代理,超时设置可能会意外断开连接。记得把代理的超时时间调大,或者直接关掉代理的缓冲。
Nginx 配置 SSE 时要注意:
location /api/stream {
proxy_pass http://backend;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
}
proxy_buffering off 是关键配置。不开缓冲的话,Nginx 不会等后端返回完整响应再转发,而是立即把数据推送给客户端。
实际应用场景
实时通知系统:后台管理系统里的待办事项提醒、审批通知,用 SSE 推送非常合适。用户不用刷新页面,新消息实时出现在右上角。
数据仪表盘:监控大屏展示 CPU 使用率、内存占用、请求量等指标。服务器每秒推送最新数据,前端实时更新图表。
进度条反馈:文件上传、模型训练、数据处理这些耗时操作,用 SSE 实时推送进度百分比,用户体验比轮询好太多了。
多人协作编辑:虽然最终方案可能是 WebSocket,但在只需要单向同步的场景下,SSE 也能胜任。比如协作文档里的只读预览。
总结
SSE 是一个被低估的技术。它没有 WebSocket 那么炫酷,但胜在简单、稳定、兼容性好。
如果你的场景是服务器单向推送数据,SSE 几乎是最优解。几行代码就能跑起来,不需要额外的基础设施,浏览器原生支持,还能利用现有的 HTTP 缓存和代理机制。
下次再做实时功能的时候,不妨先想想:我真的需要 WebSocket 吗?也许 SSE 就够了。