SSE(Server-Sent Events,服务器发送事件)是一种由服务端向客户端“单向推送数据”的通信协议,基于 HTTP 协议实现,特别适合进行实时更新的场景,例如:
- 通知中心(实时通知)
- 股票价格变动
- 实时日志流
- 聊天室中消息广播(注意不能替代 WebSocket 的双向通信)
1. SSE 工作原理
客户端使用 JavaScript 发起一个 HTTP 请求,告诉服务端它希望接收“事件流”(Event Stream)数据,服务端保持这个连接并不断向客户端推送数据。
1.1 客户端代码示例(JavaScript)
const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
console.log("收到数据:", event.data);
};
eventSource.onerror = function(err) {
console.error("连接出错", err);
};上面代码中/sse是服务端路由路径,客户端要连接到哪个路径,要看服务端怎么设计的。
1.2 服务端代码实例(Node.js Express)
app.get('/stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
setInterval(() => {
res.write(`data: ${new Date().toISOString()}\n\n`);
}, 1000);
});1.3 服务端响应格式(text/event-stream)
服务端返回的响应头:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive响应体格式示例(纯文本格式):
data: Hello World
\n
data: Another line
\n支持字段:
data::事件数据event::自定义事件名称id::事件 ID(断线重连时有用)retry::客户端重连时间(毫秒)
示例:
id: 123
event: new_message
data: {"msg": "hello"}
\n2. SSE vs WebSocket vs Long Polling
| 特性 | SSE | WebSocket | Long Polling |
|---|---|---|---|
| 通信方向 | 单向(服务端 → 客户端) | 双向 | 单向,模拟 |
| 协议 | HTTP(标准) | 自定义协议(ws://) | HTTP |
| 兼容性 | 高(现代浏览器支持) | 需要浏览器支持 | 高 |
| 重连机制 | 内置 | 自实现 | 自实现 |
| 使用复杂度 | 低 | 中 | 中 |
| 场景 | 实时通知、事件广播 | 聊天、游戏 | 简单推送场景 |
3. 注意事项
- 连接数限制:浏览器对每个源的 EventSource 连接数有限(Chrome 通常为 6)。
- 不支持跨域 withCredentials:默认不能携带 cookie,如果需要,需要设置 CORS。
- 不能发送客户端数据:如需客户端 → 服务端交互,需额外请求(XHR/Fetch)。
- 防止连接超时:需要定期发送 keep-alive 数据。
注意
标准 EventSource 不允许跨域携带 cookie,哪怕设置了 withCredentials。 某些浏览器可能实现了非标准方式(比如 polyfill),但要真正支持,服务端也要设置 CORS。
4. 调试方式
可以直接用 curl 模拟 SSE 客户端:
curl -N http://localhost:5000/sse
# -v:启用verbose模式,会显示请求和响应头,有助于调试
curl -N -v -H "Accept: text/event-stream" http://localhost:5000/ssecurl -N 中的 -N 是 --no-buffer 的简写,意思是: 不要对输出进行缓冲,立即把服务端返回的内容原样输出到终端。
当用 curl 调试像 SSE(Server-Sent Events)这样的“流式响应”时,如果没有 -N,curl 会缓冲数据,直到收到一整块才显示,这就看不到实时更新效果了。
5. Cloudflare Workers示例代码
import { Hono } from "hono"
const app = new Hono<{ Bindings: CloudflareBindings }>()
app.get('/sse', (c) => {
const encoder = new TextEncoderStream()
const writer = encoder.writable.getWriter()
writer.write(`data: 欢迎加入广播大厅\n\n`)
const heartbeat = () => {
writer.write(`: keep-alive\n\n`).catch(() => {
writer.close()
})
}
const intervalId = setInterval(heartbeat, 5000)
const abortHandler = () => {
console.log('Client disconnected')
clearInterval(intervalId)
writer.close()
c.req.raw.signal.removeEventListener('abort', abortHandler)
}
c.req.raw.signal.addEventListener('abort', abortHandler)
// 直接返回 TextEncoderStream.readable 作为字节流
return new Response(encoder.readable, {
status: 200,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
},
})
})
export default app;6. SSE连接的建立和服务端推流
6.1 连接建立
客户端通过浏览器或其他HTTP客户端,向服务器发起一个HTTP请求(通常是GET请求),请求头中会带有Accept: text/event-stream告诉服务器它希望建立一个SSE连接。
服务器收到请求后,响应头会设置:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive服务器保持这个HTTP连接持续打开状态,不断地往这个连接里写数据,而不是像普通HTTP响应那样一次性返回内容。
浏览器收到这个响应后,会把这个连接当做一个“事件流”来处理,等待服务器不断推送事件数据。
6.2 服务端推流
服务器端持续往这个HTTP响应流中写入数据,格式遵循SSE规范,典型格式如下:
data: 这是推送给客户端的消息内容\n\n- 每条消息以
data:开头 - 消息行结束后必须有一个空行,代表一条消息结束
- 如果需要推送多行消息,可以多次使用
data: - 可以指定事件类型(
event:)、ID(id:)、重连时间(retry:)等字段