基于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 那么强大,但在 "大模型一次请求,流式响应" 这个业务场景下,它是简单、成本低、兼容性最好的选择。

相关推荐
满栀58517 分钟前
vue3动态路由详细效果
前端·javascript·vue.js·typescript·前端框架
使用小功能大师19 分钟前
从零搭建高可用Web应用:全栈架构实战与成本优化完全指南
前端·阿里云·架构·服务搭建
IT_陈寒30 分钟前
SpringBoot这个特性差点让我加班到凌晨
前端·人工智能·后端
qq_2676128933 分钟前
Gitee DevOps的度量驱动持续改进:从构建数据到交付效率的量化管理与优化路径
前端·gitee·自动化
meilindehuzi_a37 分钟前
从 Vite 到 Axios 与 Mock:React Todos 全栈项目架构及请求链路详解
前端·react.js·架构
亿元程序员39 分钟前
项目经理让我在 2D 项目里做 3D 音乐球球,我只好做了个“假的”
前端
糖果店的幽灵42 分钟前
Codex官网前端可抄吗?从模仿到创新的技术实践指南
前端·人工智能
youqingyike43 分钟前
【无标题】
前端·qt
恋猫de小郭1 小时前
Jetpack Compose 8 月版正式发布,核心模块 1.12
android·前端·flutter
纵道软件1 小时前
使用 Docker 构建自定义 SeaTunnel Web 服务镜像
前端·docker·容器