分析效果
使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的“打字机”效果呈现,这种体验的背后,隐藏着一种高效的Web技术。它就是服务器推送事件(Server-Sent Events,简称 SSE)
什么是 SSE
从根本上说,SSE 是一种允许服务器单向、持续地向客户端(浏览器)推送数据的Web技术。
可以把它想象成一个电台广播:
- 服务器是广播电台,持续不断地播出节目。
- 客户端是收音机,一旦调到正确的频道,就能持续收听,无需反复请求“现在有什么新节目吗?”。
SSE 仍然是 HTTP 协议,它建立一个从服务器到客户端的长连接,服务器可以随时通过这个连接发送数据,而客户端只需被动接收即可。
如何使用
SSE 的实现非常简洁,其核心流程如下:
发起连接:浏览器用 new EventSource('/your-stream-endpoint')
创建一个实例,给服务器发一个 HTTP GET 请求。
保持连接:服务器收到请求后,发送带特殊响应头 Content-Type: text/event-stream
的响应,并保持该连接开启。
发送数据:服务器以特定的文本格式(data: ...\n\n
)将事件和数据块发送给客户端
实现流式聊天应用
用Bun启动一个本地服务器,实现sse接口
代码如下
1import { serve } from "bun";
2import PageApp from "./chat.html";
3serve({
4 port: 3000,
5 routes: {
6 "/api/chat": {
7 async GET(req) {
8 const url = new URL(req.url);
9 const message = url.searchParams.get('message');
10 if (!message) return new Response("消息不能为空", { status: 400 });
11
12
13 const stream = new ReadableStream({
14 async start(controller) {
15 const response = `man,hahahaha,what can I say,mambaout!`;
16
17 for (const char of response) {
18 const data = `data: ${JSON.stringify({ content: char })}\n\n`;
19 controller.enqueue(new TextEncoder().encode(data));
20 await new Promise((resolve) => setTimeout(resolve, 50));
21 }
22 controller.close();
23 },
24 });
25
26 return new Response(stream, {
27 headers: {
28 "Content-Type": "text/event-stream",
29 "Cache-Control": "no-cache",
30 },
31 });
32 },
33 },
34 "/": PageApp,
35 },
36 async fetch(req) {
37 return new Response("404", { status: 404 });
38 },
39});
40
41console.log("http://localhost:3000");
42
43
前端代码
1// 连接 SSE
2const eventSource = new EventSource(`/api/chat?message=${encodeURIComponent(message)}`)
3
4eventSource.onmessage = (event) => {
5 const data = JSON.parse(event.data)
6 botDiv.textContent += data.content
7}
8
9eventSource.onerror = () => {
10 eventSource.close()
11}
实际效果
打开浏览器控制台可以看到消息是一个字符一个字符发送的
它的消息通常还有以下字段 核心字段:
data
: 这是消息的数据载荷。例如:data: {"price": 123.45, "time": "14:30:00"}
event
: 自定义事件的名称。如果没有这个字段,默认就是message
事件。例如:event: stock_update
id
: 事件的唯一标识符。retry
: 告知浏览器在断线重连前应等待多少毫秒。:
(冒号开头的行): 这是注释行,会被客户端忽略。一个常见的用途是作为“心跳包”,防止因网络长时间无活动而被某些防火墙或代理中断连接。
总结
所以,SSE 的原理可以归结为:
- 利用 HTTP 的灵活性:通过特定的
Content-Type
改变了 HTTP 请求的“一次性”行为模式,将其转变为持久连接。 - 定义简单的文本协议:规定了
data
,id
,event
等字段和\n\n
分隔符,使得数据传输既简单又可扩展。 - 在浏览器端内置智能:将连接管理、数据解析和自动重连+断点续传等复杂逻辑封装在
EventSource
API 中,极大地简化了开发者的工作。
它并非发明了一个全新的网络协议,而是对现有 Web 基础设施的一次巧妙“再利用”,因此具有极好的兼容性和易用性。
个人笔记记录 2021 ~ 2025