正在加载,请稍候…

WebSocket vs SSE vs 长轮询:何时使用哪种技术

比较 WebSocket、Server-Sent Events 和长轮询在实时 Web 应用中的差异,涵盖延迟、浏览器支持、服务器负载及选择建议。

WebSocket vs SSE vs 长轮询:何时使用哪种技术

实时通信问题

当你的 Web 应用需要从服务器接收更新——聊天消息、实时比分、股票价格、通知——主要有三种选择:WebSocket、Server-Sent Events (SSE) 和长轮询。它们在连接方式、成本和失败场景上存在根本差异。

选择错误会导致资源浪费、扩展困难或用户体验不佳。本指南将详细解释每种技术的工作原理,并提供清晰的选择标准。

WebSocket vs SSE vs 长轮询:何时使用哪种技术 示意图

长轮询

工作原理

长轮询是最古老的技术,不需要特殊的浏览器 API。

  1. 客户端发送常规 HTTP 请求
  2. 服务器保持连接打开(不立即响应)
  3. 当新数据可用时,服务器响应
  4. 客户端立即发送另一个请求
  5. 重复
// 客户端
async function longPoll() {
  while (true) {
    try {
      const res = await fetch('/api/events?lastId=' + lastEventId);
      const data = await res.json();
      processEvent(data);
      lastEventId = data.id;
    } catch (err) {
      // 连接断开,等待后重试
      await new Promise(r => setTimeout(r, 2000));
    }
  }
}

longPoll();
// 服务器 (Node.js / Express)
app.get('/api/events', async (req, res) => {
  const lastId = req.query.lastId;

  // 等待最多 30 秒获取新数据
  const data = await waitForNewData(lastId, { timeout: 30000 });

  if (data) {
    res.json(data);
  } else {
    res.status(204).end();  // 超时,无新数据
  }
});

优缺点

优点:

  • 通用性强——任何 HTTP 服务器和客户端都支持
  • 透明地通过防火墙和代理
  • 利用现有基础设施易于实现
  • 无需粘性会话即可与标准负载均衡器配合

缺点:

  • 每次请求都有 HTTP 开销(头部、HTTP/1.1 的 TCP 握手开销)
  • 延迟较高——每个事件至少一次往返
  • 服务器保持连接打开,消耗线程/内存
  • 并非真正的双向通信——客户端必须始终发起

最佳适用场景: 基础设施受限的环境、更新不频繁的简单通知系统或遗留系统。

Server-Sent Events (SSE)

WebSocket vs SSE vs 长轮询:何时使用哪种技术 示意图

工作原理

SSE 使用单个持久 HTTP 连接,服务器可以随时通过该连接推送数据。

  1. 客户端使用 EventSource 打开一个 HTTP 连接
  2. 服务器流式传输 text/event-stream 数据
  3. 连接保持打开;服务器在事件发生时发送事件
  4. 如果连接断开,浏览器自动重新连接
// 客户端——仅需两行
const es = new EventSource('/api/stream');

es.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
};

// 命名事件类型
es.addEventListener('user-joined', (event) => {
  console.log('User joined:', event.data);
});

es.addEventListener('error', () => {
  if (es.readyState === EventSource.CLOSED) {
    console.log('Connection closed');
  }
});
// 服务器 (Node.js / Express)
app.get('/api/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // 发送注释以保持连接活跃
  const keepAlive = setInterval(() => {
    res.write(': ping\n\n');
  }, 20000);

  // 发送数据
  const sendEvent = (type, data) => {
    res.write(`event: ${type}\n`);
    res.write(`data: ${JSON.stringify(data)}\n`);
    res.write(`id: ${Date.now()}\n\n`);  // 用于重连恢复的 id
  };

  // 订阅事件总线
  const unsubscribe = eventBus.on('update', (data) => sendEvent('update', data));

  req.on('close', () => {
    clearInterval(keepAlive);
    unsubscribe();
  });
});

优缺点

优点:

  • 原生浏览器重连,支持 Last-Event-ID 头部
  • 比 WebSocket 更简单——纯 HTTP,与标准基础设施兼容
  • 出色的浏览器支持(所有现代浏览器)
  • 事件可以包含类型、ID 和重试间隔
  • 支持 HTTP/2 多路复用(每个连接多个流)

缺点:

  • 单向——仅服务器到客户端;客户端需单独发送请求
  • HTTP/1.1 下每个域名最多 6 个并发连接(HTTP/2 无此限制)
  • 仅文本(UTF-8);无法高效传输二进制数据
  • 某些企业代理会缓冲响应,破坏流式传输

最佳适用场景: 通知、实时信息流、仪表盘、实时日志——任何数据从服务器流向客户端,客户端通过常规 POST 请求偶尔发送更新的场景。

WebSocket

工作原理

WebSocket 以 HTTP 连接开始,然后通过 Upgrade 握手升级为全双工 TCP 连接。

  1. 客户端发送 HTTP Upgrade 请求
  2. 服务器响应 101 Switching Protocols
  3. 双方现在可以随时发送消息,双向均可
  4. 消息可以是文本或二进制(ArrayBuffer / Blob)
// 客户端
const ws = new WebSocket('wss://api.example.com/ws');

ws.onopen = () => {
  console.log('Connected');
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'prices' }));
};

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

ws.onclose = (event) => {
  console.log('Closed:', event.code, event.reason);
  // 实现指数退避重连
  setTimeout(reconnect, Math.min(30000, 1000 * 2 ** retryCount++));
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

// 从客户端发送
function sendMessage(data) {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify(data));
  }
}
// 服务器 (Node.js / ws 库)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws, req) => {
  console.log('Client connected from', req.socket.remoteAddress);

  ws.on('message', (message) => {
    const data = JSON.parse(message);
    // 处理 data.type === 'subscribe', 'unsubscribe', 'message' 等
    handleClientMessage(ws, data);
  });

  ws.on('close', (code, reason) => {
    console.log('Client disconnected:', code, reason.toString());
    cleanupClient(ws);
  });

  ws.on('error', (err) => {
    console.error('WebSocket error:', err);
  });

  // Ping 检测失效连接
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });
});

// 心跳——终止断开的连接
setInterval(() => {
  wss.clients.forEach(ws => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

WebSocket vs SSE vs 长轮询:何时使用哪种技术 示意图

优缺点

优点:

  • 真正的双向通信——客户端和服务器均可随时推送
  • 初始握手后每条消息开销低(2–10 字节帧头 vs HTTP 的数百字节)
  • 支持二进制数据(图片、音频、文件块)
  • 高频消息场景下延迟极低(游戏、交易)

缺点:

  • 基础设施需支持 WebSocket(负载均衡器、代理需配置)
  • 无自动重连——需自行实现
  • 有状态连接使水平扩展复杂化(需要粘性会话或发布/订阅层)
  • 在受限企业网络中可能遇到防火墙/代理问题

最佳适用场景: 聊天、多人在线游戏、实时协作(类似 Google Docs)、金融交易仪表盘、任何客户端频繁发送消息的应用。

并排对比

特性 长轮询 SSE WebSocket
方向 客户端 → 服务器 → 客户端 服务器 → 客户端 双向
协议 HTTP HTTP WebSocket (TCP)
二进制数据 通过编码 不支持 支持
自动重连 手动 内置 手动
浏览器支持 通用 所有现代浏览器 所有现代浏览器
HTTP/2 支持 多路复用 不适用
代理/防火墙友好 ✅ 最佳 ✅ 良好 ⚠️ 需配置
消息开销 高(完整 HTTP) 最小
扩展复杂度 低-中

决策指南

使用长轮询如果:

  • 需要最大的基础设施兼容性
  • 更新不频繁(少于每 30 秒一次)
  • 位于严格代理或旧负载均衡器之后
  • 需要快速实现,无需新基础设施

使用 SSE 如果:

  • 数据主要从服务器流向客户端
  • 希望简单且具有原生浏览器重连
  • 构建通知、实时信息流、进度更新
  • 使用 HTTP/2(SSE 变得非常高效)

使用 WebSocket 如果:

  • 需要真正的双向消息传递(聊天、游戏、协作)
  • 发送二进制数据
  • 消息频率高(每秒几次以上)
  • 延迟至关重要(交易、游戏)

扩展考虑

长轮询和 SSE 更容易扩展,因为它们使用标准 HTTP——任何无状态负载均衡器均可工作。WebSocket 连接是有状态的,因此负载均衡器必须将同一客户端路由到同一服务器(粘性会话),或者需要发布/订阅层(Redis、NATS),以便任何服务器都可以向任何已连接的客户端发布消息。

对于大规模 WebSocket 部署,可考虑:Socket.IO(处理重连、房间)、Ably、Pusher 或托管的 WebSocket 服务。

→ 使用 URL 解析器 检查 WebSocket 端点 URL 及其查询参数。