副标题:实时双向通信的技术选型指南
在需要双向实时通信的场景中,Streamable-HTTP 和 WebSocket 是两种主流方案。虽然它们都能实现实时数据交换,但在协议层级、连接模型和适用场景上有本质区别。
WebSocket 是一种独立的应用层协议,通过 HTTP 握手后升级为一个持久的全双工 TCP 连接。
┌─────────────────────────────────────────────────────┐
│ WebSocket 连接建立 │
├─────────────────────────────────────────────────────┤
│ │
│ 客户端 服务器 │
│ │ │ │
│ │── GET /ws HTTP/1.1 ─────→│ (HTTP 握手) │
│ │ Connection: Upgrade │ │
│ │ Upgrade: websocket │ │
│ │ │ │
│ │←── 101 Switching ────────│ (协议升级) │
│ │ Protocols │ │
│ │ │ │
│ ════ WebSocket 连接建立 ════ │
│ │ │ │
│ │←──── 双向数据帧 ─────────→│ (全双工通信) │
│ │←──── 双向数据帧 ─────────→│ │
│ │ │ │
│ [持久 TCP 连接] [持久 TCP 连接] │
│ │
└─────────────────────────────────────────────────────┘
| 特性 | 说明 |
|---|---|
| 协议 | WebSocket (ws:// / wss://) |
| 传输层 | 独立 TCP 连接 |
| 通信方向 | 全双工(同时双向) |
| 握手 | HTTP 101 协议升级 |
| 数据帧 | 二进制或文本帧 |
| 浏览器支持 | IE10+ 及所有现代浏览器 |
| 代理穿透 | 可能受阻(需配置) |
// WebSocket 客户端
const ws = new WebSocket('wss://example.com/ws');
// 连接建立
ws.onopen = () => {
console.log('连接成功');
ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }));
};
// 接收消息
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('收到:', data);
};
// 发送消息
function sendMessage(msg) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'message', content: msg }));
}
}
// 错误处理
ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
};
// 连接关闭
ws.onclose = (event) => {
console.log('连接关闭:', event.code, event.reason);
// 需要手动重连
setTimeout(reconnect, 3000);
};
Streamable-HTTP 是标准 HTTP 协议的扩展使用,通过 Transfer-Encoding: chunked 实现流式数据传输。
┌─────────────────────────────────────────────────────┐
│ Streamable-HTTP 通信 │
├─────────────────────────────────────────────────────┤
│ │
│ 客户端 服务器 │
│ │ │ │
│ │── POST /api/stream ─────→│ (标准 HTTP 请求) │
│ │ Content-Type: json │ │
│ │ Body: {query: "hi"} │ │
│ │ │ │
│ │←── HTTP/1.1 200 OK ──────│ │
│ │ Transfer-Encoding: │ │
│ │ chunked │ │
│ │ │ │
│ │←── 数据块 1 ──────────────│ │
│ │←── 数据块 2 ──────────────│ │
│ │←── 数据块 N ──────────────│ │
│ │ │ │
│ [HTTP 响应流] [按需生成数据] │
│ │ │ │
│ │── POST /api/send ───────→│ (新请求发送数据) │
│ │ │ │
└─────────────────────────────────────────────────────┘
| 特性 | 说明 |
|---|---|
| 协议 | 标准 HTTP/1.1 或 HTTP/2 |
| 传输层 | 复用 HTTP 连接 |
| 通信方向 | 请求-响应(可双向通过多个请求) |
| 握手 | 无(标准 HTTP) |
| 数据格式 | 任意(JSON、文本、二进制) |
| 浏览器支持 | 所有支持 fetch 的浏览器 |
| 代理穿透 | 无障碍(标准 HTTP) |
// Streamable-HTTP 客户端
async function streamRequest(query) {
const response = await fetch('/api/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ query })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
console.log('收到块:', chunk);
}
}
// 发送数据(新请求)
async function sendData(data) {
await fetch('/api/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
| 对比维度 | WebSocket | Streamable-HTTP |
|---|---|---|
| 协议 | WebSocket (独立协议) | HTTP (标准协议) |
| URL 前缀 | ws:// / wss:// | http:// / https:// |
| 连接建立 | HTTP 握手 + 协议升级 | 标准 HTTP 请求 |
| 通信模式 | 全双工(同时双向) | 半双工(请求-响应) |
| 连接持久性 | 长期保持 | 按需建立 |
| 数据帧 | 二进制/文本帧 | 任意 HTTP 响应体 |
| 消息开销 | 低(2-14 字节头部) | 中(HTTP 头开销) |
| 延迟 | 极低(已建立连接) | 低(每次请求开销) |
| 浏览器支持 | IE10+ | 所有现代浏览器 |
| 移动端支持 | 良好 | 极佳 |
| 代理/防火墙 | 可能受阻 | 通常无障碍 |
| 负载均衡 | 复杂(粘性会话) | 简单(无状态) |
| 认证 | 握手时一次 | 每次请求可带认证 |
| 自动重连 | 需手动实现 | 需手动实现 |
| 心跳机制 | 需手动实现 | HTTP Keep-Alive |
| 二进制数据 | 原生支持 | Base64 或原始字节 |
| 多路复用 | 需额外实现 | HTTP/2 原生支持 |
| 服务器资源 | 高(维持连接) | 中(短连接或长连接) |
| CDN 支持 | 有限 | 完整 |
| 调试难度 | 中(需专用工具) | 低(标准 HTTP 工具) |
┌─────────────────────────────────────────────────────┐
│ WebSocket 架构 │
├─────────────────────────────────────────────────────┤
│ │
│ 客户端 服务器 其他客户端 │
│ │ │ │ │
│ │── 握手 ─────────→│ │ │
│ │ │ │ │
│ ════════════════════════ │
│ │ 持久连接 │ │ │
│ │←── 广播消息 ───────┼──────────────→│ │
│ │ │ │ │
│ │── 发送消息 ──────→│ │ │
│ │ │── 转发 ──────→│ │
│ │ │ │ │
│ [保持长连接] [维护连接状态] [保持长连接] │
│ │
│ 特点: │
│ - 真正的实时双向 │
│ - 低延迟 │
│ - 服务器需维护连接状态 │
│ - 扩展性挑战 │
│ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Streamable-HTTP 架构 │
├─────────────────────────────────────────────────────┤
│ │
│ 客户端 服务器 其他客户端 │
│ │ │ │ │
│ │── 请求 1 ────────→│ │ │
│ │←── 流式响应 ───────│ │ │
│ │ │ │ │
│ │── 请求 2 ────────→│ │ │
│ │ │── 通知 ──────→│ │
│ │ │ │ │
│ │── 请求 3 ────────→│ │ │
│ │←── 流式响应 ───────│ │ │
│ │ │ │ │
│ [按需连接] [可状态可无状态] [按需连接] │
│ │
│ 特点: │
│ - 标准 HTTP,兼容性好 │
│ - 易于扩展和负载均衡 │
│ - 每次请求有开销 │
│ - 实时性稍逊于 WebSocket │
│ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 协议栈对比 │
├─────────────────────────────────────────────────────┤
│ │
│ WebSocket Streamable-HTTP │
│ ─────────── ───────────────── │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ WebSocket │ │ Application │ │
│ │ Protocol │ │ (JSON/etc) │ │
│ └─────────────┘ └─────────────┘ │
│ ↓ ↓ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ TCP │ │ HTTP/1.1 or │ │
│ │ │ │ HTTP/2 │ │
│ └─────────────┘ └─────────────┘ │
│ ↓ ↓ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ IP │ │ TCP │ │
│ └─────────────┘ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ IP │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
| 场景 | WebSocket | Streamable-HTTP |
|---|---|---|
| 首包延迟 | ~100-300ms(握手) | ~50-100ms |
| 后续消息 | ~1-10ms | ~30-80ms(新请求) |
| 大规模广播 | ~N ms | 不适用 |
| 指标 | WebSocket | Streamable-HTTP |
|---|---|---|
| 单连接吞吐量 | 极高 | 高 |
| 并发连接数 | 10K-100K | 无限制(短连接) |
| 消息开销 | 2-14 字节 | ~几百字节(HTTP 头) |
| 资源 | WebSocket | Streamable-HTTP |
|---|---|---|
| 服务器内存 | 高(每连接占用) | 低(请求完成释放) |
| 文件描述符 | 持续占用 | 短暂占用 |
| CPU 开销 | 低(事件驱动) | 中(HTTP 解析) |
✅ 高频实时双向通信
✅ AI 大模型应用
// ========== 客户端 ==========
const ws = new WebSocket('wss://chat.example.com');
const chatBox = document.getElementById('chat');
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
appendMessage(msg);
};
function sendMessage(text) {
ws.send(JSON.stringify({
type: 'chat',
content: text,
timestamp: Date.now()
}));
}
// ========== 服务端 (Node.js) ==========
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('message', (data) => {
const msg = JSON.parse(data);
// 广播给所有客户端
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(msg));
}
});
});
ws.on('close', () => {
clients.delete(ws);
});
});
// ========== 客户端 ==========
// 接收消息(长轮询 SSE 风格)
async function receiveMessages() {
const response = await fetch('/chat/stream', {
headers: { 'Authorization': 'Bearer token' }
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split('\n');
for (const line of lines) {
if (line.trim()) {
const msg = JSON.parse(line);
appendMessage(msg);
}
}
}
}
// 发送消息
async function sendMessage(text) {
await fetch('/chat/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ content: text })
});
}
// ========== 服务端 (Node.js) ==========
const express = require('express');
const app = express();
const clients = new Map();
// 接收消息
app.post('/chat/send', (req, res) => {
const { content } = req.body;
const msg = { content, timestamp: Date.now() };
// 广播给所有等待的客户端
clients.forEach((res, id) => {
res.write(JSON.stringify(msg) + '\n');
});
res.json({ success: true });
});
// 流式接收
app.get('/chat/stream', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Transfer-Encoding', 'chunked');
const clientId = Date.now();
clients.set(clientId, res);
req.on('close', () => {
clients.delete(clientId);
});
});
实际项目中,可以结合两者优势:
┌─────────────────────────────────────────────────────┐
│ 混合架构示例 │
│ (类似现代聊天应用) │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 首次连接 / 认证 │ │
│ │ Streamable-HTTP │ │
│ │ POST /auth + 获取初始历史消息 │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 建立实时通道 │ │
│ │ WebSocket │ │
│ │ wss:// 用于高频双向消息 │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 文件上传 / 复杂 API │ │
│ │ Streamable-HTTP │ │
│ │ 标准 HTTP 用于非实时操作 │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
是否需要双向高频实时通信?
│
┌───┴───┐
│ │
是 否
│ │
▼ ▼
┌─────────────┐ ┌─────────────────────┐
│ 延迟要求 │ │ 是否需要流式输出? │
│ < 50ms? │ │ │
└─────────────┘ └─────────────────────┘
│ │
┌───┴───┐ ┌────┴────┐
│ │ │ │
是 否 是 否
│ │ │ │
▼ ▼ ▼ ▼
WebSocket ┌─────────────────┐ 标准 HTTP
│ Streamable-HTTP │ 即可
└─────────────────┘
| WebSocket | Streamable-HTTP |
|---|---|
| 专用实时协议 | 标准 HTTP 扩展 |
| 全双工、低延迟 | 半双工、标准兼容 |
| 复杂部署、高资源 | 简单部署、低资源 |
| 游戏、聊天、协作 | AI 应用、简单流式 |
| 需要专门基础设施 | 现有 HTTP 基础设施 |
WebSocket = 极致实时性能,但代价是复杂性 Streamable-HTTP = 足够好的实时性,但获得简单性和兼容性
研究时间:2026-03-07
标签: #WebSocket #StreamableHTTP #实时通信 #协议对比 #Web开发 #MCP #小凯
还没有人回复