轮询与 WebSocket 对比

短轮询、长轮询和 WebSocket 三种实时通信方案的区别与实现

问题

对比短轮询、长轮询 (Long Polling) 与 WebSocket 三种实时通信方案。

解答

短轮询 (Short Polling)

客户端定时向服务器发送请求,无论是否有新数据。

// 客户端:每 3 秒请求一次
function shortPolling() {
  setInterval(async () => {
    const response = await fetch('/api/messages')
    const data = await response.json()
    if (data.length > 0) {
      console.log('新消息:', data)
    }
  }, 3000)
}

特点:实现简单,但浪费资源,实时性差。

长轮询 (Long Polling)

服务器收到请求后不立即响应,等到有新数据或超时才返回。

// 客户端:递归请求
async function longPolling() {
  try {
    const response = await fetch('/api/messages/subscribe')
    const data = await response.json()
    console.log('收到数据:', data)
  } catch (error) {
    console.error('连接错误:', error)
  }
  // 无论成功失败,立即发起下一次请求
  longPolling()
}

// 服务端 (Node.js + Express)
app.get('/api/messages/subscribe', (req, res) => {
  const checkForMessages = () => {
    const messages = getNewMessages()
    if (messages.length > 0) {
      res.json(messages)
    } else {
      // 没有消息,等待 1 秒后再检查,最多等 30 秒
      setTimeout(checkForMessages, 1000)
    }
  }
  
  // 30 秒超时
  setTimeout(() => res.json([]), 30000)
  checkForMessages()
})

特点:比短轮询实时性好,但仍是单向通信,每次需要重新建立连接。

WebSocket

全双工通信协议,建立持久连接后双方可随时发送数据。

// 客户端
const ws = new WebSocket('ws://localhost:8080')

ws.onopen = () => {
  console.log('连接已建立')
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'chat' }))
}

ws.onmessage = (event) => {
  const data = JSON.parse(event.data)
  console.log('收到消息:', data)
}

ws.onclose = () => {
  console.log('连接已关闭')
  // 可以在这里实现重连逻辑
}

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

// 发送消息
function sendMessage(message) {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify(message))
  }
}
// 服务端 (Node.js + ws 库)
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })

wss.on('connection', (ws) => {
  console.log('客户端已连接')
  
  ws.on('message', (message) => {
    const data = JSON.parse(message)
    console.log('收到:', data)
    
    // 广播给所有客户端
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ type: 'broadcast', data }))
      }
    })
  })
  
  ws.on('close', () => {
    console.log('客户端已断开')
  })
})

对比表格

特性短轮询长轮询WebSocket
实时性
服务器压力
通信方向单向单向双向
连接方式每次新建每次新建持久连接
实现复杂度
兼容性需要支持

使用场景

// 短轮询:适合更新频率低、实时性要求不高的场景
// 例如:检查是否有新版本
setInterval(() => checkForUpdates(), 60000)

// 长轮询:适合兼容性要求高、消息频率不高的场景
// 例如:邮件通知

// WebSocket:适合高实时性、双向通信的场景
// 例如:聊天室、实时协作、游戏、股票行情

关键点

  • 短轮询:定时请求,实现简单但浪费带宽,延迟等于轮询间隔
  • 长轮询:服务器 hold 住请求直到有数据,减少无效请求,但仍需频繁建立连接
  • WebSocket:基于 TCP 的全双工协议,一次握手后保持连接,适合高频实时通信
  • 选择依据:根据实时性要求、消息频率、兼容性需求选择合适方案
  • WebSocket 注意:需要处理心跳保活、断线重连、消息确认等问题