JavaScript长轮询
外观
JavaScript长轮询(Long Polling)是一种模拟实时通信的技术,通过延长HTTP请求的响应时间,使服务器能在数据可用时立即向客户端推送信息。它是传统轮询(Polling)的优化方案,适用于需要实时更新但无法使用WebSocket的场景。
概述[编辑 | 编辑源代码]
长轮询结合了轮询和推送技术的优点。其工作流程如下:
- 客户端发送HTTP请求到服务器。
- 服务器保持连接打开,直到有新数据或超时。
- 当数据到达或超时后,服务器响应请求,客户端立即处理数据并发送新请求。
与短轮询(频繁请求)相比,长轮询减少了无效请求,降低了网络开销。
数学原理[编辑 | 编辑源代码]
假设事件到达间隔服从参数为的泊松分布,长轮询的平均延迟为: 其中为超时时间。
实现方法[编辑 | 编辑源代码]
基础实现[编辑 | 编辑源代码]
以下是使用原生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);
}
}
时序图[编辑 | 编辑源代码]
应用场景[编辑 | 编辑源代码]
实时通知系统[编辑 | 编辑源代码]
当用户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的环境中构建高效的准实时应用系统。