websocket和sse有什么区别呢?
WebSocket 和 SSE(Server-Sent Events,服务端推送事件)都是实现服务器向客户端实时推送数据的技术,但它们在设计理念、协议和使用场景上有明显区别。
以下是两者的详细对比:
一、核心区别对比表
| 特性 | WebSocket | SSE (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); };
三、如何选择?
-
选 WebSocket 的情况:
- 如果你的应用需要客户端主动向服务器发送数据(不仅仅是响应),并且服务器也需要随时回复。
- 例如:聊天室、多人游戏、协同编辑。
-
选 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?
| 对比项 | HTTP | WebSocket |
|---|---|---|
| 是否流式 | ❌ 难 | ✅ 天然支持 |
| 延迟 | 高 | 极低 |
| 连续对话 | 不方便 | 非常适合 |
| 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 在大模型中的真实差异