webSocket和SSE

5 阅读6分钟

websocket和sse有什么区别呢?

WebSocket 和 SSE(Server-Sent Events,服务端推送事件)都是实现服务器向客户端实时推送数据的技术,但它们在设计理念、协议和使用场景上有明显区别。

以下是两者的详细对比:

一、核心区别对比表

特性WebSocketSSE (Server-Sent Events)
通信模式全双工(双向通信)单工(单向通信)
协议ws://wss://(独立的 TCP 协议)HTTP/HTTPS(基于 HTTP 长连接)
方向性客户端 ↔ 服务器 (两者可随时发消息)服务器 → 客户端 (仅服务器推送)
连接数量单个 TCP 连接通常也是单连接(HTTP/2 下可多路复用)
断线重连需要手动实现心跳检测和重连逻辑原生支持​ (EventSource自动重连)
数据格式文本或二进制 (自定义格式,如 JSON)仅文本​ (默认为 text/event-stream)
浏览器支持IE 10+ (所有现代浏览器)除 IE/Edge 旧版本外的大部分现代浏览器
复杂度相对较高 (需处理连接状态、帧等)非常简单 (浏览器原生 API)

二、详细解析

1. WebSocket(全双工通信)

WebSocket 提供了一个真正的持久连接,允许客户端和服务器在任何时间点互相发送数据,而不需要重新建立连接。

  • 工作原理:首先通过一次 HTTP 请求进行“握手”(Handshake),将连接从 HTTP 升级(Upgrade) ​ 到 WebSocket 协议,之后便在同一个 TCP 连接上进行双向数据传输。

  • 适用场景

    • 即时聊天应用(微信网页版、在线客服)。
    • 多人协作工具(如腾讯文档、在线白板)。
    • 实时游戏
    • 需要高频、低延迟双向交互的场景。
  • 代码示例(前端)

    const socket = new WebSocket('ws://localhost:8080/ws');
    
    // 连接建立时触发
    socket.onopen = function() {
        console.log('Connected!');
        socket.send('Hello Server!'); // 客户端向服务端发送消息
    };
    
    // 接收服务端消息
    socket.onmessage = function(event) {
        console.log('Message from server:', event.data);
    };
    
    // 连接关闭
    socket.onclose = function() {
        console.log('Disconnected.');
    };
    

2. SSE(服务器推送事件)

SSE 是一种单向通信技术,允许服务器通过一次初始的 HTTP 请求,源源不断地向客户端推送数据流。

  • 工作原理:客户端发起一个普通的 HTTP 请求,服务器响应后不关闭连接,而是保持连接打开,并通过这个连接持续发送数据(格式为 text/event-stream)。

  • 适用场景

    • 股票行情看板
    • 新闻/社交媒体动态流
    • 日志监控(服务器实时推送日志到前端)。
    • 通知提醒(只需服务器推送给客户端)。
  • 代码示例(前端)

    // 浏览器原生支持 EventSource
    const eventSource = new EventSource('http://localhost:8080/sse');
    
    // 监听默认消息事件
    eventSource.onmessage = function(event) {
        console.log('New message:', event.data);
    };
    
    // 监听自定义事件(如 'stock')
    eventSource.addEventListener('stock', function(event) {
        console.log('Stock update:', event.data);
    });
    
    // 监听错误(如连接断开,EventSource 会自动尝试重连)
    eventSource.onerror = function(err) {
        console.error('EventSource failed:', err);
    };
    

三、如何选择?

  1. 选 WebSocket 的情况:

    • 如果你的应用需要客户端主动向服务器发送数据(不仅仅是响应),并且服务器也需要随时回复。
    • 例如:聊天室、多人游戏、协同编辑。
  2. 选 SSE 的情况:

    • 如果你只需要服务器单方面向客户端推送数据
    • 你希望利用现有的 HTTP 基础设施(代理、认证、SSL)。
    • 你需要简单的重连机制和轻量级协议。
    • 例如:实时新闻、股票报价、状态更新。

四、补充:与 HTTP 轮询的区别

这两种技术都是为了克服传统 HTTP 请求-响应​ 模式的不足:

  • 短轮询:客户端定时不断发请求问“有更新吗?”(浪费资源)。
  • 长轮询:客户端发请求,服务器有数据才返回,返回后立即再发新请求(比短轮询好,但仍需频繁重建连接)。
  • SSE/WebSocket:建立一次连接,长期保持(更高效)。

简单总结需要双向聊天用 WebSocket;只需要听广播用 SSE。

**

比如说现在大多数大模型都是使用的websoket,例如用户传输了两个连续的问题,前端页面怎么输出答案呢?

这是一个非常典型、也非常贴近实际大模型应用(如 ChatGPT 网页版)的问题。下面我用结合 WebSocket + 大模型流式输出的角度,把整个流程和前端处理方式讲清楚。


一、先说结论(核心认知)

**大多数大模型并不是“一次性返回答案”,而是通过 WebSocket(或 SSE)进行「流式输出(streaming)」**​

👉 前端是边接收、边渲染的,而不是等全部回答完再显示。

即使你连续发了两个问题,只要模型是逐个 token / chunk 返回,前端都可以做到:

  • ✅ 实时打字机效果
  • ✅ 两个问题的回答互不干扰
  • ✅ 回答顺序正确

二、典型的大模型 WebSocket 通信模式

1️⃣ 建立一次 WebSocket 连接(不是每个问题一次)

const ws = new WebSocket('wss://api.example.com/chat');

连接只建立 一次,后续所有问题都走这条通道。


2️⃣ 用户发送第一个问题

ws.send(JSON.stringify({
  type: 'chat',
  id: 'msg-001',
  content: '什么是 WebSocket?'
}));

3️⃣ 后端开始“流式”返回答案(token by token)

后端并不是一次性 return完整答案,而是类似这样:

"Web"
"Socket"
" 是"
"一种"
"全双工"
...

在 WebSocket 里,通常会拆成多个 frame:

{ "id": "msg-001", "delta": "Web" }
{ "id": "msg-001", "delta": "Socket" }
{ "id": "msg-001", "delta": " 是" }

4️⃣ 前端「边收边渲染」

前端核心逻辑:

let currentText = '';

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.id === 'msg-001') {
    currentText += msg.delta;
    document.getElementById('answer-1').innerText = currentText;
  }
};

✅ 看起来就像 打字机效果


三、用户连续发第二个问题怎么办?

这是你关心的重点 👇

情况一:串行(最常见)

等第一个问题回答完,再发第二个问题

Q1 → 流式回答 Q1
Q2 → 流式回答 Q2

✅ 最简单

✅ 不容易乱序

✅ ChatGPT 默认就是这种


情况二:并行(高级用法)

两个问题几乎同时发出,模型同时流式返回

Q1 ──┐
     ├─> 交错返回 token
Q2 ──┘

完全可行

✅ 依赖「消息 ID」来区分回答归属


四、前端如何区分「这是哪个问题的回答」

✅ 关键设计:每个问题都有唯一 messageId

前端发送

ws.send(JSON.stringify({
  type: 'chat',
  messageId: 'uuid-123',
  content: '第一个问题'
}));

ws.send(JSON.stringify({
  type: 'chat',
  messageId: 'uuid-456',
  content: '第二个问题'
}));

后端返回(流式)

{ "messageId": "uuid-123", "delta": "第一题的部分答案" }
{ "messageId": "uuid-456", "delta": "第二题的部分答案" }
{ "messageId": "uuid-123", "delta": "继续第一题" }

前端渲染(核心代码)

const answerMap = {
  'uuid-123': '',
  'uuid-456': ''
};

ws.onmessage = (event) => {
  const { messageId, delta } = JSON.parse(event.data);

  answerMap[messageId] += delta;

  document.getElementById(`answer-${messageId}`).innerText =
    answerMap[messageId];
};

✅ 两个回答完全不会串

✅ DOM 各自更新

✅ 支持并发流式输出


五、为什么不用 HTTP 接口,而要用 WebSocket?

对比项HTTPWebSocket
是否流式❌ 难✅ 天然支持
延迟极低
连续对话不方便非常适合
token 成本不可控可中断

👉 大模型 token 是按量计费的,流式返回还能:

  • 用户中途停止
  • 前端提前展示
  • 减少感知延迟

六、真实生产级优化点(进阶)

✅ 1. 回答中插入 Markdown / 代码块

// 边收边渲染 markdown
answer.innerHTML = marked.parse(currentText);

✅ 2. 用户中断回答(Stop)

ws.send({
  type: 'stop',
  messageId: 'uuid-123'
});

✅ 3. 心跳 & 重连

setInterval(() => ws.send({ type: 'ping' }), 30000);

七、一句话总结

大模型使用 WebSocket 的本质:

  • 不是“一次请求一次回答”
  • 而是 一条连接 + 多条消息 + 流式 token + messageId 区分归属

如果你愿意,我可以:

  • ✅ 给你画一张「前后端交互时序图」
  • ✅ 写一个 最小可用 Demo(前端 + Spring Boot / Node.js)
  • ✅ 对比 WebSocket vs SSE 在大模型中的真实差异