Skip to content

SSE(Server-Sent Events)

介绍

SSE(Server-Sent Events) 是一种服务器向客户端单向推送数据的技术。

  • 基于 HTTP 协议:不需要像 WebSocket 那样建立独立连接。
  • 单向通信:只能服务器推送给客户端,客户端不能通过 SSE 发送消息。
  • 自动重连:断线后浏览器会自动尝试重连。
  • 轻量级:比 WebSocket 简单,适合只需要服务器推送的场景。

客户端实现

EventSource API(标准 SSE)

适用于简单的服务器推送场景,浏览器原生支持。

优点

  • ✅ 浏览器原生支持,API 简单
  • ✅ 自动重连机制
  • ✅ 自动解析 SSE 格式

限制

  • ❌ 只能发送 GET 请求
  • ❌ 不能自定义请求头(如 Authorization)
  • ❌ 不能发送请求体

适用场景

  • 服务器主动推送通知
  • 实时数据更新(股票、天气等)
  • 进度条更新
js
// 建立连接
const eventSource = new EventSource('/api/sse')

// 监听默认消息(服务端只发送 data: 字段)
eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data)
  console.log('收到推送:', data)
}

// 监听自定义事件(服务端必须发送 event: notification 字段)
eventSource.addEventListener('notification', (event) => {
  console.log('收到通知:', event.data)
})

// 监听另一个自定义事件(服务端必须发送 event: update 字段)
eventSource.addEventListener('update', (event) => {
  console.log('收到更新:', event.data)
})

// 错误处理
eventSource.onerror = (error) => {
  console.error('连接错误', error)
}

// 关闭连接
eventSource.close()

readyState 状态

source.readyState 有 3 种状态:

  • 0:未连接。等同于 EventSource.CONNECTING
  • 1:已连接。等同于 EventSource.OPEN
  • 2:已关闭。等同于 EventSource.CLOSED

事件类型

  1. 内置事件(3 种):

    • onopen:客户端与服务器建立连接时触发
    • onmessage:接收没有 event 字段的默认消息
    • onerror:客户端与服务器连接出错时触发
  2. 自定义事件

    • 使用 addEventListener('eventName') 监听
    • 事件名必须与服务端发送的 event: 字段匹配
    • 示例:服务端发送 event: notification,客户端就用 addEventListener('notification')

Fetch + ReadableStream

适用于需要复杂请求配置的流式响应,如 AI API。

优点

  • ✅ 支持 POST/PUT 等方法
  • ✅ 可以自定义请求头和请求体
  • ✅ 更灵活的控制

限制

  • ❌ 需要手动解析 SSE 格式
  • ❌ 需要手动实现重连逻辑
  • ❌ 代码相对复杂

适用场景

  • OpenAI/DeepSeek 等 AI API 的流式响应
  • 需要认证的流式接口
  • 需要发送复杂请求体的场景
js
const API_URL = 'https://api.deepseek.com/v1/chat/completions'
const apiKey = 'your-api-key'

async function streamChat(prompt) {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model: 'deepseek-chat',
      messages: [{ role: 'user', content: prompt }],
      stream: true, // 启用流式传输
    }),
  })

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`)
  }

  // 读取流式响应
  const reader = response.body.getReader()
  const decoder = new TextDecoder('utf-8')
  let buffer = ''

  while (true) {
    const { done, value } = await reader.read()

    if (done) break

    // 解码数据块
    buffer += decoder.decode(value, { stream: true })

    // 处理 SSE 格式的数据
    const lines = buffer.split('\n')
    buffer = lines.pop() || '' // 保留不完整的行

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const data = line.slice(6)

        if (data === '[DONE]') continue

        try {
          const json = JSON.parse(data)
          const content = json.choices?.[0]?.delta?.content

          if (content) {
            console.log(content) // 实时输出内容
          }
        } catch (e) {
          console.error('解析失败:', e)
        }
      }
    }
  }
}

// 使用
streamChat('解释一下什么是 SSE')

关键点

  1. 逐块读取:使用 reader.read() 逐块读取数据
  2. 解码处理:用 TextDecoder 将字节转为文本
  3. 行缓冲:处理不完整的数据行
  4. SSE 格式解析:手动处理 data: 前缀的行

服务端实现

js
// 使用 Express 框架示例
app.get('/api/sse', (req, res) => {
  // 设置 SSE 响应头
  res.setHeader('Access-Control-Allow-Origin', '*') // 允许跨域
  res.setHeader('Content-Type', 'text/event-stream') // 告诉客户端发送的是数据流
  res.setHeader('Cache-Control', 'no-cache') // 告诉客户端不要缓存数据
  res.setHeader('Connection', 'keep-alive') // 长连接

  // 发送默认消息(只有 data 字段,客户端用 onmessage 接收)
  const sendMessage = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`)
  }

  // 发送自定义事件(有 event 字段,客户端用 addEventListener 接收)
  const sendCustomEvent = (eventName, data) => {
    res.write(`event: ${eventName}\n`)
    res.write(`data: ${JSON.stringify(data)}\n\n`)
  }

  // 定时推送不同类型的消息
  const timer = setInterval(() => {
    // 发送默认消息
    sendMessage({ message: 'Hello', time: new Date() })

    // 发送通知事件(客户端需要 addEventListener('notification') 接收)
    sendCustomEvent('notification', { msg: '有新消息' })

    // 发送更新事件(客户端需要 addEventListener('update') 接收)
    sendCustomEvent('update', { progress: 50 })
  }, 1000)

  // 客户端断开时清理
  req.on('close', () => {
    clearInterval(timer)
  })
})

常见问题

SSE vs WebSocket

特性SSEWebSocket
通信方向单向(服务器→客户端)双向
协议HTTPWebSocket 协议
重连自动重连需手动实现
浏览器支持广泛支持广泛支持
复杂度简单相对复杂
适用场景服务器推送、实时更新聊天、游戏、协作编辑

选择建议

  • 只需要服务器推送 → 使用 SSE
  • 需要双向实时通信 → 使用 WebSocket

基于 MIT 许可发布