青蛙小白
博客 / 2025/05

SSE入门:构建单向实时数据流

2025/05/04 · — 字 · 阅读约 — 分钟 ·
目录

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"}
\n

2. SSE vs WebSocket vs Long Polling

特性SSEWebSocketLong Polling
通信方向单向(服务端 → 客户端)双向单向,模拟
协议HTTP(标准)自定义协议(ws://)HTTP
兼容性高(现代浏览器支持)需要浏览器支持
重连机制内置自实现自实现
使用复杂度
场景实时通知、事件广播聊天、游戏简单推送场景

3. 注意事项

  1. 连接数限制:浏览器对每个源的 EventSource 连接数有限(Chrome 通常为 6)。
  2. 不支持跨域 withCredentials:默认不能携带 cookie,如果需要,需要设置 CORS。
  3. 不能发送客户端数据:如需客户端 → 服务端交互,需额外请求(XHR/Fetch)。
  4. 防止连接超时:需要定期发送 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/sse

curl -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:)等字段
评论