基于SSE实现AI聊天场景的流式输出

什么是SSE

SSE(Server-Sent Events),轻量级单向实时通信技术,专为 "服务器向客户端持续推送流式数据" 而设计。

SSE本质是客户端发送一个普通的HTTP请求,服务器保持连接打开,然后持续不断地向客户端推送数据。

  • 普通http:一次请求 -> 一次响应 -> 连接关闭
  • SSE:一次请求 -> 多次响应 -> 连接长期保持

SSE的特点

  1. 单向通信:只支持服务器->客户端推送,不支持客户端通过SSE向服务器发送数据
  2. 自动重连:浏览器原生支持,无需手动实现
  3. 断点续传:通过Last-Event-ID头,断线重连后可以从上次断开的位置继续接收数据
  4. http原生支持
  5. 多个SSE可以共享一个TCP连接,降低服务器开销

基于以上特点,SSE是目前AI聊天、日志监控等场景的首选方案。

工作步骤

步骤:

  1. 连接建立
  2. 服务器响应
  3. 数据推送
  4. 连接关闭

连接建立

客户端通过EventSource对象向服务器发送一个http GET请求,请求头中有Accept: text/event-stream

vbnet 复制代码
GET /api/chat HTTP/1.1
Host: example.com
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

服务器响应

yaml 复制代码
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Transfer-Encoding: chunked

数据推送

数据必须遵循SSE标准格式

yaml 复制代码
id: 123
event: message
data: 这是第一条消息

data:这是第二条消息
这还是第二条消息

retry: 5000

id: 消息id,可选,用于断点续传 event:自定义事件类型,默认是message data: 消息内容,可以跨多行 retry: 断线重连间隔多少ms

豆包网页版的SSE截图:

代码实现

前端示例

ts 复制代码
window.mockChat = () => {
  // 创建SSE连接
  const eventSource = new EventSource('http://localhost:3000/api/chat?prompt=你好')

  // 监听默认message事件
  eventSource.onmessage = (e) => {
    console.log('收到消息:', e.data)
  }

  // 监听自定义事件
  eventSource.addEventListener('complete', (e) => {
    console.log('回答生成完成')
    eventSource.close() // 关闭连接
  })

  // 监听错误事件
  eventSource.onerror = (e) => {
    // @todo
  }
}

服务端示例

js 复制代码
import express from 'express'

const app = express()

app.get('/api/chat', (req, res) => {
  // 设置SSE响应头
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
    'Access-Control-Allow-Origin': '*',
  })

  // 模拟大模型流式生成回答
  const answer = '你好,我是AI助手,很高兴为你服务!'
  let index = 0

  const timer = setInterval(() => {
    if (index < answer.length) {
      // 每次发送一个字
      res.write(`data: ${answer[index]}\n\n`)
      index++
    } else {
      // 发送完成事件
      res.write('event: complete\n')
      res.write('data: done\n\n')
      clearInterval(timer)
      res.end()
    }
  }, 100)

  // 客户端关闭连接时清理资源
  req.on('close', () => {
    clearInterval(timer)
    console.log('客户端断开连接')
  })
})

app.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000')
})

效果截图

SSE和WebSocket的对比

对比维度 SSE (Server-Sent Events) WebSocket
协议基础 纯 HTTP/HTTPS 协议 GET 请求 独立的 WebSocket 协议
通信方向 单向:仅服务器→客户端推送 全双工:客户端↔服务器双向自由发送
数据格式 仅支持文本数据(UTF-8),内置事件流格式 支持二进制和文本数据,无内置格式,完全自定义
自动重连 浏览器原生支持,断线后自动重连 无原生支持,需要手动实现重连、心跳、退避算法
兼容性 所有现代浏览器支持,IE10+ 所有现代浏览器支持,IE10+
CDN / 代理支持 完美支持,可利用 CDN 缓存、边缘计算 较差,多数 CDN 不支持 WebSocket 代理
性能开销 极低,HTTP/2 支持多路复用,多个 SSE 连接共享一个 TCP 连接 中等,每个 WebSocket 连接独占一个 TCP 连接

最后

SSE 是一种轻量级、简单、可靠的实时通信技术,它没有 WebSocket 那么强大,但在 "大模型一次请求,流式响应" 这个业务场景下,它是简单、成本低、兼容性最好的选择。

相关推荐
V搜xhliang024611 小时前
AI智能体的数据安全与合规实践
人工智能·学习·数据分析·自动化·ai编程
Cutecat_12 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
qq_4221525713 小时前
PDF 加水印工具怎么选?2026 年文档版权保护方案对比
前端·pdf·github
kyriewen13 小时前
手写 Promise.all、race、any:不到 30 行代码,解决并发异步的所有姿势
前端·javascript·面试
brucelee18614 小时前
OpenClaw 浏览器控制(Chrome MCP)完整教程
前端·chrome
ct97814 小时前
React 状态管理方案深度对比
开发语言·前端·react
胡志辉的博客14 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop
代码不加糖14 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
懂懂tty14 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js
AI焦点15 小时前
跨越协议鸿沟:Tool Use状态机从Anthropic到OpenAI兼容体系的适配要点
前端·人工智能