0. 开场白:为什么 AI 喜欢"挤牙膏"?
想象一下,如果 AI 一次性把整篇《三体》丢给你,浏览器会像被三体人智子锁死的加速器------卡成 PPT 。
于是,人类发明了 Server-Sent Events(SSE) ,让 AI 像老式打字机一样,哒哒哒地边想边打,既优雅又省内存。
1. SSE 的底层原理:HTTP 的"马拉松"模式
1.1 从"问答"到"长聊"
普通 HTTP 是 "你一句我一句" 的短对话:
js
浏览器:GET /answer?q=宇宙的意义
服务器:42(完)
SSE 则是 "服务器单方面开麦" 的脱口秀:
js
浏览器:GET /stream?q=宇宙的意义
服务器:4... (别关麦!)
服务器:2... (还在想!)
服务器:! (终于想完了)
1.2 底层魔法:HTTP/1.1 的"不死连接"
SSE 本质是 一个永不关闭的 HTTP 连接,靠以下 HTTP 头实现:
http
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
就像你给服务员说:"咖啡续杯到天荒地老。"
2. 前端:如何优雅地"偷听"AI 碎碎念?
2.1 EventSource:浏览器自带的"监听器"
js
const stream = new EventSource('/ai/stream?q=如何优雅地摸鱼');
stream.onmessage = (event) => {
// AI 每吐一个字,这里就收到一次
document.body.innerHTML += event.data;
};
stream.onerror = () => {
// AI 断线了(可能去喝咖啡了)
console.error('AI 掉线了,正在重连...');
};
2.2 手动解析 SSE 格式(硬核玩家)
SSE 数据格式像 "键值对+换行" 的极简诗:
text
data: 你
data: 好
data: 世
data: 界
event: close
data: (说完收工)
用 JS 手动解析:
js
const decoder = new TextDecoder();
const reader = response.body.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
console.log('AI说:', line.slice(6));
}
}
}
3. 后端:如何让服务器变成"话痨"?
3.1 Node.js 极简实现(Express 版)
js
const express = require('express');
const app = express();
app.get('/ai/stream', (req, res) => {
// 设置 SSE 响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 每秒发一个字(假装 AI 在思考)
const message = '你好,我是 AI,正在用 0.3 倍速思考人生...';
let index = 0;
const interval = setInterval(() => {
if (index >= message.length) {
res.write('event: close\ndata: \n\n');
clearInterval(interval);
res.end();
return;
}
res.write(`data: ${message[index]}\n\n`);
index++;
}, 100);
});
app.listen(3000, () => console.log('AI 电台已开播:http://localhost:3000'));
3.2 处理"断线重连"的哲学问题
浏览器会在 连接断开时自动重连(默认 3 秒),后端需要优雅处理:
js
// 客户端重连时,从上次位置继续
const lastEventId = req.headers['last-event-id'];
if (lastEventId) {
// 从第 N 个字开始续播
index = parseInt(lastEventId, 10);
}
4. 高阶技巧:让 AI 像 Rapper 一样押韵
4.1 压缩输出(节省带宽)
用 gzip
压缩 SSE 数据(需前端配合):
js
res.setHeader('Content-Encoding', 'gzip');
4.2 多路复用(一个连接 N 个话题)
SSE 原生只支持 单通道 ,但可以通过 event:
字段模拟多路:
text
event: joke
data: 为什么 AI 不失眠?因为它没有床。
event: answer
data: 因为 42。
前端区分事件:
js
stream.addEventListener('joke', (e) => {
console.log('AI 讲了个笑话:', e.data);
});
5. 彩蛋:SSE 的"暗黑料理"
5.1 用 SSE 实现"服务器推送广告"
js
setInterval(() => {
res.write('event: ad\ndata: 买它!AI 同款显卡只要 999!\n\n');
}, 5000);
5.2 用 SSE 做"实时弹幕"
前端用 CSS 让文字从右向左飞:
css
.barrage {
position: fixed;
white-space: nowrap;
animation: fly 5s linear;
}
@keyframes fly {
from { transform: translateX(100vw); }
to { transform: translateX(-100%); }
}
6. 结语:当 AI 学会"拖延术"
SSE 的本质是 "用时间换空间" ------
让用户等 5 秒,但能省 50MB 内存。
就像你老板说的:"慢慢做,做得慢才不会出错。"
"AI 不是变快了,只是学会了优雅地拖延 。"
------某 SSE 协议开发者,在 404 酒吧的酒后真言