前端实时通信方案对比:WebSocket vs SSE vs setInterval 轮询

455 阅读4分钟

📢 前言

在前端开发中,实时数据获取是常见需求。本文从前端视角对比三种主流方案:WebSocketSSE (Server-Sent Events)setInterval 轮询,帮你做出最佳技术选型。


📊 核心对比表格

特性WebSocketSSEsetInterval 轮询
通信方向全双工(双向通信)单工(服务端→客户端)半双工(客户端主动拉取)
实时性毫秒级亚秒级依赖轮询间隔(最低秒级)
协议类型独立协议(ws:///wss://基于 HTTP 长连接基于 HTTP 短连接
前端复杂度高(需管理连接状态)中(事件监听)低(定时器 + fetch)
移动端电量消耗高(频繁唤醒射频模块)
兼容性IE10+Edge12+(需 Polyfill)全兼容

🔍 技术方案详解

1. WebSocket

📌 核心特性

  • 双向实时通信:客户端与服务端可同时发送消息
  • 低延迟:适用于高频交互场景(如在线游戏、聊天室)
  • 二进制支持:可传输 ArrayBuffer、Blob 等二进制数据

💻 前端实现

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

// 连接成功
ws.onopen = () => {
  console.log('WebSocket connected');
  ws.send('Hello Server!');
};

// 接收消息
ws.onmessage = (event) => {
  console.log('Received:', event.data);
};

// 错误处理
ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

// 关闭连接
ws.onclose = () => {
  console.log('WebSocket closed');
};

⚠️ 注意事项

  • 需手动实现心跳检测防止断开
  • iOS 后台运行超过 30 秒可能断开连接
  • 浏览器同一域名最多保持 6 个 WebSocket 连接

2. SSE (Server-Sent Events)

📌 核心特性

  • 服务端推送:服务端可主动向客户端推送数据
  • 自动重连:内置断线重连机制(默认 3 秒)
  • 轻量级:基于 HTTP 协议,兼容现有基础设施

💻 前端实现

const sse = new EventSource('/api/sse');

// 监听自定义事件
sse.addEventListener('stock_update', (e) => {
  console.log('Stock price:', JSON.parse(e.data));
});

// 通用消息监听
sse.onmessage = (e) => {
  console.log('Message:', e.data);
};

// 错误处理(会自动重连)
sse.onerror = () => {
  console.error('SSE connection error');
};

⚠️ 注意事项

  • 不支持 IE 浏览器(需使用 eventsource polyfill)
  • 只能传输文本数据(二进制需 Base64 编码)
  • Chrome 同一域名最多 6 个 SSE 连接

3. setInterval 轮询

📌 核心特性

  • 简单易用:无需服务端特殊支持
  • 兼容性极佳:所有浏览器均支持
  • 成本可控:适合低频更新场景

💻 前端实现

let pollTimer;

// 启动轮询
function startPolling() {
  pollTimer = setInterval(async () => {
    try {
      const res = await fetch('/api/data');
      const data = await res.json();
      updateUI(data);
    } catch (err) {
      console.error('Polling error:', err);
    }
  }, 5000); // 5秒间隔
}

// 停止轮询
function stopPolling() {
  clearInterval(pollTimer);
}

// 页面隐藏时暂停轮询
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    stopPolling();
  } else {
    startPolling();
  }
});

⚠️ 注意事项

  • 高频轮询会导致性能问题(推荐间隔 ≥30 秒)
  • 移动端电量消耗显著增加
  • 可能产生陈旧数据(需配合 ETag 等机制)

🚀 选型建议

决策流程图

是否需要双向通信?
├── 是 → 选择 WebSocket
└── 否 → 是否需要服务端主动推送?
       ├── 是 → 选择 SSE
       └── 否 → 数据更新频率?
               ├── ≤30秒 → 优先考虑 SSE
               └── >30秒 → setInterval 轮询

场景化推荐

场景推荐方案理由
在线聊天/多人游戏WebSocket需要双向高频通信
股票行情/实时日志SSE服务端主动推送,节省带宽
天气预报/配置更新setInterval 轮询低频请求,实现简单

🛠 优化技巧

WebSocket

🔧 ​​心跳包与 Nginx 超时的关系​

​核心原则​

  • ​心跳间隔 < Nginx 超时时间​
    例如:若 Nginx 配置 proxy_read_timeout 60s,则建议心跳间隔设为 ​​50-55秒​​,需满足:

    心跳间隔 + 网络抖动缓冲 < Nginx超时时间
    
  • 使用二进制协议(如 Protocol Buffers)压缩数据

  • 添加心跳包检测连接状态

// WebSocket 心跳机制
const HEARTBEAT_INTERVAL = 50 * 1000; // 50秒
let heartbeatTimer;

ws.onopen = () => {
  // 开启心跳
  heartbeatTimer = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: 'heartbeat' }));
    }
  }, HEARTBEAT_INTERVAL);

  // 重置心跳计数器
  ws.onmessage = () => {
    clearTimeout(heartbeatTimer);
    heartbeatTimer = setInterval(...); // 重新计时
  };
};

SSE

  • 利用 lastEventId 实现断点续传
// 从 localStorage 读取最后事件ID
let lastEventId = localStorage.getItem('lastSSEId') || '0';

const sse = new EventSource(`/api/sse?lastId=${lastEventId}`);

sse.addEventListener('message', (e) => {
  // 更新最后事件ID
  lastEventId = e.lastEventId;
  localStorage.setItem('lastSSEId', lastEventId);
  
  // 处理数据
  console.log('收到数据:', e.data);
});

sse.addEventListener('error', () => {
  sse.close();
  setTimeout(() => {
    // 断线重连时自动携带 lastEventId
    new EventSource(`/api/sse?lastId=${lastEventId}`);
  }, 3000);
});

setInterval 轮询

  • 使用智能退避策略
let retryCount = 0;
function smartPoll() {
  fetch('/api/data')
    .then(resetRetry)
    .catch(() => {
      const delay = Math.min(2000 * 2 ** retryCount, 30000);
      setTimeout(smartPoll, delay);
      retryCount++;
    });
}

💡 总结

方案核心优势最佳场景
WebSocket双向实时通信在线协作、实时游戏
SSE服务端推送 + 自动重连监控仪表盘、实时通知
setInterval 轮询简单易用 + 全兼容低频数据更新、兼容性要求高场景

最终建议:根据业务需求选择合适方案,对于复杂场景可组合使用(如 WebSocket + SSE)实现最佳效果!