跳转到内容

JavaScript长轮询

来自代码酷


JavaScript长轮询(Long Polling)是一种模拟实时通信的技术,通过延长HTTP请求的响应时间,使服务器能在数据可用时立即向客户端推送信息。它是传统轮询(Polling)的优化方案,适用于需要实时更新但无法使用WebSocket的场景。

概述[编辑 | 编辑源代码]

长轮询结合了轮询和推送技术的优点。其工作流程如下:

  1. 客户端发送HTTP请求到服务器。
  2. 服务器保持连接打开,直到有新数据或超时。
  3. 当数据到达或超时后,服务器响应请求,客户端立即处理数据并发送新请求。

与短轮询(频繁请求)相比,长轮询减少了无效请求,降低了网络开销。

数学原理[编辑 | 编辑源代码]

假设事件到达间隔服从参数为λ的泊松分布,长轮询的平均延迟为: E[D]=1λ(1eλT) 其中T为超时时间。

实现方法[编辑 | 编辑源代码]

基础实现[编辑 | 编辑源代码]

以下是使用原生JavaScript和XMLHttpRequest的实现:

function longPolling(url, callback, timeout = 30000) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.timeout = timeout;

    xhr.onload = function() {
        if (xhr.status === 200) {
            callback(JSON.parse(xhr.responseText));
        }
        // 立即发起下一次请求
        longPolling(url, callback, timeout);
    };

    xhr.onerror = xhr.ontimeout = function() {
        // 错误处理后重新连接
        setTimeout(() => longPolling(url, callback, timeout), 1000);
    };

    xhr.send();
}

// 使用示例
longPolling('/api/updates', data => {
    console.log('Received:', data);
});

Fetch API 实现[编辑 | 编辑源代码]

现代JavaScript推荐使用Fetch API:

async function fetchLongPolling(url, callback, timeout = 30000) {
    try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);

        const response = await fetch(url, { 
            signal: controller.signal 
        });
        clearTimeout(timeoutId);

        if (response.ok) {
            const data = await response.json();
            callback(data);
        }
    } catch (error) {
        console.error('Polling error:', error);
    } finally {
        fetchLongPolling(url, callback, timeout);
    }
}

时序图[编辑 | 编辑源代码]

ClientServerloop[数据检查]alt[有新数据][超时]GET /updates(保持连接)检查新数据返回数据(200 OK)空响应(204)立即发起新请求ClientServer

应用场景[编辑 | 编辑源代码]

实时通知系统[编辑 | 编辑源代码]

当用户A触发事件时,服务器立即将通知推送给用户B:

// 客户端
longPolling('/api/notifications', notifications => {
    notifications.forEach(showToast);
});

// 服务器伪代码
app.get('/api/notifications', (req, res) => {
    waitForNewNotifications(req.userId, 30)
        .then(notifications => res.json(notifications))
        .catch(() => res.status(204).end());
});

股票价格更新[编辑 | 编辑源代码]

金融应用需要实时显示价格变动:

let lastPrice = null;
fetchLongPolling('/api/stock/AAPL', ({ price }) => {
    if (price !== lastPrice) {
        updatePriceChart(price);
        lastPrice = price;
    }
});

优化策略[编辑 | 编辑源代码]

1. 退避算法:在连续错误时增加重试间隔

   let retryDelay = 1000;
   function handleError() {
       retryDelay = Math.min(retryDelay * 2, 60000);
       setTimeout(connect, retryDelay);
   }

2. 心跳机制:防止连接被代理服务器关闭

   setInterval(() => {
       fetch('/api/heartbeat');
   }, 25000);

对比其他技术[编辑 | 编辑源代码]

技术 协议 实时性 服务器开销
短轮询 HTTP
长轮询 HTTP
WebSocket WS
SSE HTTP

注意事项[编辑 | 编辑源代码]

  • 超时设置:通常设置在20-40秒之间,超过多数代理服务器的连接保持时间
  • 错误处理:必须实现自动重连机制
  • 并发限制:浏览器对同一域名的并发连接数有限制(通常6个)
  • 状态管理:服务器需要跟踪每个客户端的上次数据状态

进阶主题[编辑 | 编辑源代码]

分布式系统挑战[编辑 | 编辑源代码]

在微服务架构中,需要:

  • 使用Redis等共享存储保存客户端状态
  • 实现消息总线广播变更
  • 处理跨服务器的连接迁移

性能优化[编辑 | 编辑源代码]

1. 二进制数据传输(如Protocol Buffers) 2. 请求压缩(gzip) 3. 增量更新(只发送变化部分)

通过合理实现长轮询,可以在不支持WebSocket的环境中构建高效的准实时应用系统。