土薯工具 Toolshu.com 登錄 用戶注冊

SSE 原理與實戰:AI 打字機效果背後的技術,以及它和 WebSocket 的區別

原創 作者:bhnw 於 2026-04-09 09:48 發佈 3次瀏覽 收藏 (0)

AI 打字機效果是怎麼實現的?

用過 ChatGPT 或任何國產大模型的人都見過這個效果:AI 回答不是一次性出現,而是一個字一個字地"打"出來,像有人在實時打字。

這個效果背後用的技術叫 SSE(Server-Sent Events,服務器推送事件)。理解 SSE,不僅能搞清楚 AI 打字機效果的原理,更能在自己的項目裏實現實時推送、直播彈幕、股價更新等場景。


SSE 是什麼?

SSE 是一種基於 HTTP 的單向實時通信協議,允許服務器主動向客戶端推送數據,而無需客戶端反覆發起請求。

它的工作方式很簡單:客戶端發起一個普通的 HTTP 請求,服務器不立刻關閉連接,而是保持連接打開,持續往裏"寫"數據——每次有新內容就推一條,直到流結束。

客戶端                    服務器
  |                         |
  |--- GET /stream -------->|
  |                         |
  |<-- data: 第一條消息 ----|
  |<-- data: 第二條消息 ----|
  |<-- data: 第三條消息 ----|
  |         ...             |
  |<-- data: [DONE] --------|  連接關閉

SSE 的數據格式

SSE 的數據格式非常簡單,每條消息由若干字段組成,字段之間用換行分隔,消息之間用空行分隔:

data: 這是第一條消息

data: 這是第二條消息

event: update
data: {"type":"progress","value":80}
id: 42
retry: 3000

字段說明:

字段 說明
data 消息內容,必填,可以多行
event 自定義事件類型,默認爲 message
id 消息 ID,用於斷線重連時告知服務器上次收到的位置
retry 重連等待時間(毫秒),客戶端斷開後等多久重連

Content-Type 必須是 text/event-stream,否則瀏覽器不會按 SSE 協議解析。


AI 流式輸出的 SSE 長什麼樣?

OpenAI、通義千問等大模型 API 的流式輸出,正是 SSE 格式。每個 token 生成後立刻推送一條:

data: {"id":"1","choices":[{"delta":{"content":"你"},"finish_reason":null}]}

data: {"id":"2","choices":[{"delta":{"content":"好"},"finish_reason":null}]}

data: {"id":"3","choices":[{"delta":{"content":"!"},"finish_reason":null}]}

data: [DONE]

前端收到每條 data 後,解析 JSON,提取 choices[0].delta.content,拼接到界面上,就實現了打字機效果。

調試這類 AI 接口時,如果想實時查看每條 SSE 數據的內容並自動提取指定 JSON 字段,可以用 SSE 在線調試工具,輸入接口地址和 Header(如 Authorization token),直接在瀏覽器裏觀察流式數據。


前端如何接收 SSE?

瀏覽器原生提供了 EventSource API,專門用於接收 SSE:

const source = new EventSource('/api/stream');

// 接收默認 message 事件
source.onmessage = (event) => {
  console.log('收到消息:', event.data);
};

// 接收自定義事件類型
source.addEventListener('update', (event) => {
  const data = JSON.parse(event.data);
  console.log('進度更新:', data.value);
});

// 錯誤處理
source.onerror = (error) => {
  console.error('連接出錯', error);
  source.close();
};

侷限EventSource 只支持 GET 請求,不能自定義請求頭。如果需要 POST 或攜帶 Authorization header(比如調用需要鑑權的 AI 接口),需要用 fetch + ReadableStream 手動處理:

const response = await fetch('/api/stream', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token'
  },
  body: JSON.stringify({ prompt: '你好' })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const text = decoder.decode(value);
  // 解析 SSE 格式,提取 data 字段
  const lines = text.split('\n');
  for (const line of lines) {
    if (line.startsWith('data: ')) {
      const data = line.slice(6);
      if (data === '[DONE]') return;
      console.log(JSON.parse(data));
    }
  }
}

後端如何實現 SSE?

Python(FastAPI)

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

async def event_generator():
    for i in range(5):
        yield f"data: 第{i+1}條消息\n\n"
        await asyncio.sleep(1)
    yield "data: [DONE]\n\n"

@app.get("/stream")
async def stream():
    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream"
    )

Node.js(Express)

app.get('/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  let count = 0;
  const interval = setInterval(() => {
    res.write(`data: 第${++count}條消息\n\n`);
    if (count >= 5) {
      res.write('data: [DONE]\n\n');
      res.end();
      clearInterval(interval);
    }
  }, 1000);

  req.on('close', () => clearInterval(interval));
});

SSE vs WebSocket:怎麼選?

這是最常見的問題。兩者都能實現實時通信,但適用場景不同:

對比項 SSE WebSocket
通信方向 單向(服務器→客戶端) 雙向
協議 基於 HTTP 獨立協議(ws://)
斷線重連 瀏覽器自動重連 需手動實現
實現複雜度 簡單 較複雜
兼容性 所有現代瀏覽器 所有現代瀏覽器
適用場景 通知推送、AI流式輸出、實時日誌 在線遊戲、聊天室、協同編輯
穿透代理/CDN 更容易(普通HTTP) 需要代理支持 ws

選擇原則:如果只需要服務器向客戶端推數據,用 SSE——更簡單,基於普通 HTTP,不需要特殊的服務器或代理配置。如果需要客戶端也主動向服務器發消息(真正的雙向實時通信),才用 WebSocket。

大多數 AI 應用的流式輸出場景,SSE 完全夠用,沒必要上 WebSocket。


幾個容易踩的坑

1. Nginx 代理緩衝導致數據不實時

SSE 經過 Nginx 時,如果開啓了代理緩衝,數據會積攢到一定量才一起發出,實時性全無。需要在配置裏關掉:

location /stream {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_set_header Connection '';
    proxy_http_version 1.1;
    chunked_transfer_encoding on;
}

2. HTTP/2 下的行爲差異

HTTP/2 原生支持多路複用,SSE 在 HTTP/2 下表現更好,不會佔用額外的 TCP 連接。但部分舊版代理對 HTTP/2 + SSE 的支持不完善,出問題時可以嘗試降級到 HTTP/1.1。

3. 連接數限制

瀏覽器對同一域名的 HTTP/1.1 連接數有限制(通常6個)。如果頁面打開多個 SSE 連接,可能耗盡連接數影響其他請求。HTTP/2 下沒有這個問題。

发现周边 发现周边
評論區

加載中...