🔍 深入拆解:从逐字打印效果看 SSE 流式技术奥秘
一、现象观察:GPT 的逐字打印魔法
当我们与 ChatGPT 对话时,最令人着迷的体验莫过于文字的逐字涌现——每个字符像被无形之手逐个敲出。这种看似简单的效果背后藏着怎样的技术玄机?
开发者工具抓包解密:
- 打开 Chrome 网络面板,过滤
XHR/Fetch请求 - 观察响应类型:
Content-Type: text/event-stream - 数据接收方式:分段流式传输(Streaming)
👉 关键发现:使用 fetch() + ReadableStream + SSE 协议组合
效果查看

二、技术选型:为什么是 SSE?
协议对决表
| 维度 | SSE | WebSocket | 长轮询 |
|---|---|---|---|
| 连接方式 | 单工 (服务端推流) | 全双工 | 半双工 |
| 协议基础 | HTTP/HTTPS | 独立协议(ws/wss) | HTTP/HTTPS |
| 数据格式 | text/event-stream | 二进制/文本 | JSON/Text |
| 心跳机制 | 内置自动维护 | 需手动实现 | 无 |
| 内存消耗 | 低 (TCP 长连接) | 中 | 高 (频繁重建连接) |
| 适用场景 | 实时日志、金融行情、对话流 | 即时聊天、高频交易 | 简单状态更新 |
三、SSE 核心技术解析
1. 协议工作原理图
sequenceDiagram
客户端->>+服务端: GET /chat-stream (SSE连接)
服务端->>+AI引擎: 获取响应内容
loop 流式处理
AI引擎-->>服务端: 返回数据分片
服务端-->>客户端: data: "{content:'xx'}"
end
服务端-->>客户端: [end]事件
deactivate 服务端
2. 消息结构规范
# 标准事件格式
event: message\n
data: {"content": "Hello"}\n\n
# 自定义事件
event: customEvent\n
data: 自定义内容\n\n
# 错误处理
retry: 3000\n # 重连间隔(毫秒)
四、逐字流式实现架构
五、代码实现
全链路流程图
graph LR
A[前端] -->|1. 发起 SSE 请求| B(Nginx)
B -->|2. 代理转发| C[Node 服务]
C -->|3. 流式生成| D[AI 大模型]
D -->|分片返回| C
C -->|streaming 回传| B
B -->|4. 数据中继| A
A -->|5. 拆字渲染| E[DOM 元素]
客户端代码(vue + fetch)
new Vue({
el: "#chat-app",
data: {
messages: [],
inputMessage: "",
isSending: false,
currentBotMessage: null,
},
methods: {
async sendMessage() {
if (!this.inputMessage.trim() || this.isSending) return;
// 状态重置
this.isSending = true;
const userMessage = this.inputMessage;
this.inputMessage = "";
// 添加用户消息
this.messages.push({
text: userMessage,
sender: "user",
timestamp: Date.now(),
});
// 添加初始机器人消息
const botMessage = {
text: "",
sender: "bot",
loading: true,
timestamp: Date.now(),
};
this.messages.push(botMessage);
this.currentBotMessage = botMessage;
try {
const response = await fetch(
"http://localhost:9527/chat/stream",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: userMessage }),
}
);
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
const reader = response.body
.pipeThrough(new TextDecoderStream())
.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
value.split("\n").forEach((line) => {
if (line.startsWith("data: ")) {
const payload = line.slice(6).trim();
if (payload === "[DONE]") return;
try {
const data = JSON.parse(payload);
this.currentBotMessage.text += data.content;
this.keepScrollBottom();
} catch (e) {
console.warn("Parse error:", e);
}
}
});
}
} catch (error) {
console.error("Request failed:", error);
this.currentBotMessage.text =
"⚠️ Connection error: " + error.message;
} finally {
this.isSending = false;
this.currentBotMessage.loading = false;
this.keepScrollBottom();
}
},
keepScrollBottom() {
this.$nextTick(() => {
const container = this.$el.querySelector(".chat-window");
container.scrollTop = container.scrollHeight;
});
},
},
});
服务端代码(node + express)
const express = require("express");
const cors = require("cors");
const app = express();
// 中间件配置
app.use(express.json()).use(cors({ origin: "*" }));
// 流式接口核心逻辑
app.post("/chat/stream", (req, res) => {
const { message } = req.body;
console.log(`Received: ${message}`);
// 初始化 SSE 协议头
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
// 定义响应数据流
const responseStream = createStreamResponse();
// 管道式推送数据
const interval = setInterval(() => {
const { done, value } = responseStream.next();
if (!done) {
res.write(`data: ${JSON.stringify(value)}\n\n`);
} else {
res.write("event: end\ndata: [DONE]\n\n");
clearInterval(interval);
res.end();
}
}, 100);
// 客户端断开处理
// req.on("close", () => {
// clearInterval(interval);
// res.end();
// });
});
// 模拟流式生成器
function* createStreamResponse(
input = `你好!我是一名专业的前端工程师,专注于为用户创造高效、直观且美观的网络体验。我精通以下核心技术和工具`
) {
const segments = input.split("").map((c) => `${c}`);
for (const segment of segments) {
yield {
content: segment,
timestamp: Date.now(),
status: "processing",
};
}
}
// 启动服务
app.listen(9527, () => {
console.log("SSE 服务已在 9527 端口启动");
});
六、未来风向:WebTransport 的挑战
| 特性 | SSE | WebTransport |
|---|---|---|
| 协议基础 | HTTP/1.1 | HTTP/3 |
| 传输效率 | 中 | 高 (QUIC 协议加持) |
| 多路复用 | ❌ | ✅ |
| 移动端表现 | 一般 | 优 (弱网环境强适应) |
| 浏览器支持 | 主流浏览器 | Chrome 87+ |
🚨 七、常见问题速查表
| 问题现象 | 排查方向 | 解决方案 |
|---|---|---|
| 连接立即断开 | 检查响应头设置 | 确认Content-Type: text/event-stream |
| 接收数据延迟 | 服务端推送频率 | 调整分片间隔时间,避免事件循环阻塞 |
| 跨域无法连接 | CORS 配置检查 | 添加Access-Control-Allow-Origin头 |
| 长时间无数据 | 网络防火墙策略 | 检查代理服务器对长连接的保持策略 |
| 移动端连接不稳定 | 浏览器兼容性检查 | 使用 polyfill 或降级方案 |
📦 八、GitHub 代码仓库
🔗 完整实现代码仓库
🔮 结语:流式技术的艺术
SSE 如同数字世界的活水渠,在 HTTP 的土壤上开辟出持久数据流。当需要简单的服务端推送时,它仍是无可争议的首选方案。正如 GPT 的逐字打印展示的:技术选择不在高深,而在恰到好处。