Ai网站流式渲染总结
本文从技术选型分析到网站实战,将项目Ai网站中流式渲染的技术进行拆解,对比分析SSE和websocket的实现。
1. SSE简介
SSE(Server-Sent Events)译为服务器推送事件,通过EventSource接口实现服务器推送通信。与webSocket不同的是,SSE基于http连接,为单向通信,在单向推送场景下(各大AI网站)得到很好的应用。一个 EventSource 实例会对 HTTP 服务器开启一个持久化的连接,以 text/event-stream 格式发送事件,此连接会一直保持开启直到通过调用 EventSource.close() 关闭。客户端开启EventSource连接后通过监听特定的事件(如notice,update,meessage)来处理相关的逻辑。
EventSource 的特点
- 单向通信:服务器向客户端推送消息,客户端不能向服务器发送消息。
- 文本流:数据以文本形式发送,通常是 UTF-8 编码。
- 自动重连:如果连接中断,浏览器会自动尝试重新连接。
- 事件命名:可以为消息指定不同的事件类型,客户端可以根据事件类型选择性地处理消息。
sse也有一定的缺陷:
- 不支持双向通信。
- 不支持二进制数据传输。
- 兼容性存在问题,不支持 IE 浏览器。
-
单个浏览器最大连接数限制
当不使用 HTTP/2 时,服务器发送事件(SSE)受到打开连接数的限制,这个限制是对于浏览器的,并且设置为非常低的数字(6),打开多个选项卡时可能会特别痛苦。在 Chrome 和 Firefox 中,这个问题已被标记为“不会修复”。这个限制是每个浏览器和域名的,这意味着你可以在所有标签页中打开 6 个 SSE 连接到 http://www.example1.com,以及另外 6 个 SSE 连接到 http://www.example2.com(来源:Stackoverflow)。当使用 HTTP/2 时,最大并发 HTTP 流的数量是由服务器和客户端协商的(默认为 100)。
const response = await fetch(url, {method: "POST",headers: {"Content-Type": "application/json",Accept: "text/event-stream",},body: JSON.stringify(data),}).catch((err) => {console.log("err报错了", err);});// 获取 ReadableStream 并创建读取器const reader = response.body.getReader();}
使用TextDecoder 解码:
EventSource发送来的是utf-8
编码的信息,通过reader.read()
得到的value
为字节流,我们还需要使用TextDecoder 解码器器将字节流作为输入,并提供码位流作为输出,这个过程可以称为翻译。
const decoder = new TextDecoder();// 持续读取流数据while (true) {const { done, value } = await reader.read();if (done) {reader.releaseLock();break;} // 流结束console.log("字节流", value);const chunk = decoder.decode(value);console.log("解码后数据为", chunk);}
在AI网站上,我们通常会看到AI回答的内容有列表,有代码块,还有表格等等,通过观察他们的数据格式,通过引入markdown
库来对返回信息进行解析,再实时更新页面,这就有了页面打字机和丰富的排版效果。
备注:SSE需要注意通信格式:每条信息后面需以
\n\n
结束(SSE协议规定的)。 EventSource 中,发送数据的行以冒号开头表示注释行。这些行是发送给客户端的非事件数据,浏览器会忽略它们,但它们有助于保持连接活跃:// 保持连接,避免断开setInterval(() => {res.write(': keep-alive\n\n');}, 15000);
EventSource 发送数据时需要以特定的关键字开头,如 data、event、id、retry 和 :(注释行)
EventSource本质上还是Http协议,所有的Http协议都是用Http字段来跟浏览器谈心的,所以必须设置这几个header:status、content-type、cache-control、connection,不然浏览器不认识它。
HTTP/1.1 协议默认开启持久连接(persistent connection),这意味着同一个 TCP 连接可以被重用来发送和接收多个 HTTP 请求和响应。然而,如果连接在一段时间内没有活动,某些中间网络设备(如防火墙、代理服务器)或客户端本身可能会认为连接已经闲置太久,从而关闭连接。所以需要定时发送心跳让浏览器别消灭它:res.write(': keep-alive\n\n')。
2.websocket
首先对比一下SSE 和 WebSocket 流式输出的特性:
SSE (Server-Sent Events) | WebSocket | |
---|---|---|
通信方向 | 单向 (Server → Client) | 双向 (Server ↔ Client) |
协议 | HTTP/1.1 + EventSource | WebSocket 协议 |
连接方式 | 基于 HTTP 长连接 | 需要单独 升级协议 |
消息格式 | 纯文本(也支持 JSON) | 二进制 / JSON / 文本 |
断线重连 | 浏览器自动重连 | 需要手动实现重连逻辑 |
服务器推送 | 天然支持(适合 AI 生成式内容 | 需要额外实现 |
服务器压力 | 轻量级(基于 HTTP/1.1) | 服务器需要维护更多连接 |
浏览器支持 | 所有现代浏览器支持 | 需要 支持 WebSocket |
适用场景 | AI 流式输出、日志推送、股票行情等 | 实时聊天、双向交互、多人协作等 |
通过上面的对比,不难得知SSE更适合Ai生成式项目,但为什么要使用WebSocket?与 SSE 相比,WebSocket 具有以下特点:
- 支持双向通信 , 客户端可以随时向服务器发送消息,而不仅仅是等待 AI 回复。
- 更低的连接开销 ,WebSocket 连接后,数据传输比 SSE 高效,适用于高并发场景。
- 支持二进制数据,可以更灵活地发送 JSON、二进制流,适配 AI 复杂交互。
- 连接状态可控 ,可以手动断开连接、重连、主动停止 AI 生成。
综合以上,如果你的项目需要支持更多的参数和互动效果,比如手动终止以及高并发场景Websocket是不错的选择😌。