本文目录导读:

我来为您创建一个完整的长轮询模拟实时数据获取的示例代码,这个示例将模拟服务器端和客户端的长轮询通信。
服务器端代码 (Node.js + Express)
// server.js
const express = require('express');
const app = express();
const cors = require('cors');
// 允许跨域请求
app.use(cors());
app.use(express.json());
// 模拟数据存储
let updateCount = 0;
const maxWaitTime = 30000; // 最大等待时间30秒
// 模拟数据源
function generateData() {
updateCount++;
return {
id: updateCount,
timestamp: new Date().toISOString(),
data: {
temperature: Math.random() * 30 + 20,
humidity: Math.random() * 40 + 40,
pressure: Math.random() * 10 + 1000,
speed: Math.random() * 100
},
message: `这是第 ${updateCount} 次数据更新`
};
}
// 模拟异步数据更新
function simulateAsyncDataUpdate(callback) {
// 随机延迟 1-8 秒模拟数据更新
const delay = Math.random() * 7000 + 1000;
setTimeout(() => {
callback(generateData());
}, delay);
}
// 长轮询接口
app.get('/api/poll', (req, res) => {
console.log(`[${new Date().toISOString()}] 收到长轮询请求`);
// 设置超时
const timeout = setTimeout(() => {
// 超时返回空响应
res.json({
status: 'timeout',
data: null,
message: '没有新数据,请继续轮询'
});
console.log(`[${new Date().toISOString()}] 请求超时`);
}, maxWaitTime);
// 模拟数据更新
simulateAsyncDataUpdate((newData) => {
clearTimeout(timeout);
// 返回新数据
res.json({
status: 'success',
data: newData,
message: '获取到新数据'
});
console.log(`[${new Date().toISOString()}] 发送新数据: `, newData.id);
});
});
// 批量数据接口
app.get('/api/batch', (req, res) => {
const batchSize = parseInt(req.query.size) || 5;
const batchData = [];
for (let i = 0; i < batchSize; i++) {
batchData.push(generateData());
}
res.json({
status: 'success',
data: batchData,
count: batchSize,
message: `返回 ${batchSize} 条数据`
});
});
// 启动服务器
const PORT = 3000;
app.listen(PORT, () => {
console.log(`长轮询服务器已启动,端口: ${PORT}`);
console.log(`访问 http://localhost:${PORT}/api/poll 进行长轮询`);
console.log(`访问 http://localhost:${PORT}/api/batch 获取批量数据`);
});
客户端代码 (HTML + JavaScript)
<!-- client.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">长轮询实时数据获取示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 20px auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin: 20px 0;
background: #e8f4fd;
border-radius: 6px;
}
.status {
color: #666;
}
.status.active {
color: #2ecc71;
}
.status.waiting {
color: #f39c12;
}
.status.error {
color: #e74c3c;
}
.controls {
display: flex;
gap: 10px;
justify-content: center;
margin: 20px 0;
}
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.btn-start {
background: #2ecc71;
color: white;
}
.btn-start:hover {
background: #27ae60;
}
.btn-stop {
background: #e74c3c;
color: white;
}
.btn-stop:hover {
background: #c0392b;
}
.btn-start:disabled,
.btn-stop:disabled {
background: #ccc;
cursor: not-allowed;
}
.data-container {
margin: 20px 0;
}
.update-item {
background: #f9f9f9;
border-left: 4px solid #2ecc71;
padding: 15px;
margin: 10px 0;
border-radius: 0 6px 6px 0;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.update-item .time {
color: #999;
font-size: 12px;
}
.update-item .id {
color: #666;
font-weight: bold;
}
.update-item .data {
margin: 10px 0;
padding: 10px;
background: #fff;
border-radius: 4px;
}
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin: 20px 0;
}
.stat-item {
text-align: center;
padding: 15px;
background: #f0f0f0;
border-radius: 6px;
}
.stat-item .value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.stat-item .label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.error-message {
background: #fee;
color: #c00;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
display: none;
}
.max-updates-input {
margin: 20px 0;
display: flex;
gap: 10px;
align-items: center;
justify-content: center;
}
.max-updates-input input {
width: 60px;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1>📊 实时数据长轮询获取</h1>
<div class="status-bar">
<div class="status" id="statusIndicator">未连接</div>
<div id="connectionInfo">连接次数: 0</div>
<button id="clearBtn" onclick="clearData()">清除数据</button>
</div>
<div class="max-updates-input">
<label for="maxUpdates">最大更新次数:</label>
<input type="number" id="maxUpdates" value="20" min="1" max="100">
<span style="color: #999; font-size: 12px;">(0 表示不限制)</span>
</div>
<div class="stats">
<div class="stat-item">
<div class="value" id="totalUpdates">0</div>
<div class="label">总更新次数</div>
</div>
<div class="stat-item">
<div class="value" id="totalRequests">0</div>
<div class="label">总请求次数</div>
</div>
<div class="stat-item">
<div class="value" id="successRate">0%</div>
<div class="label">成功率</div>
</div>
<div class="stat-item">
<div class="value" id="avgResponseTime">0ms</div>
<div class="label">平均响应时间</div>
</div>
</div>
<div class="controls">
<button id="startBtn" class="btn-start" onclick="startPolling()">开始轮询</button>
<button id="stopBtn" class="btn-stop" onclick="stopPolling()" disabled>停止轮询</button>
</div>
<div class="error-message" id="errorMessage"></div>
<div class="data-container" id="dataContainer">
<h3>实时数据更新</h3>
<p style="color: #999;">点击"开始轮询"按钮开始获取实时数据...</p>
</div>
</div>
<script>
class LongPollingClient {
constructor(options = {}) {
this.serverUrl = options.serverUrl || 'http://localhost:3000';
this.isPolling = false;
this.currentRequest = null;
this.updateCount = 0;
this.requestCount = 0;
this.successCount = 0;
this.failCount = 0;
this.totalResponseTime = 0;
this.startTime = null;
// DOM元素引用
this.statusIndicator = document.getElementById('statusIndicator');
this.dataContainer = document.getElementById('dataContainer');
this.totalUpdates = document.getElementById('totalUpdates');
this.totalRequests = document.getElementById('totalRequests');
this.successRate = document.getElementById('successRate');
this.avgResponseTime = document.getElementById('avgResponseTime');
this.connectionInfo = document.getElementById('connectionInfo');
this.errorMessage = document.getElementById('errorMessage');
this.startBtn = document.getElementById('startBtn');
this.stopBtn = document.getElementById('stopBtn');
}
setStatus(status, message) {
this.statusIndicator.className = `status ${status}`;
this.statusIndicator.textContent = message;
}
showError(message) {
this.errorMessage.style.display = 'block';
this.errorMessage.textContent = message;
setTimeout(() => {
this.errorMessage.style.display = 'none';
}, 3000);
}
updateStats() {
this.totalUpdates.textContent = this.updateCount;
this.totalRequests.textContent = this.requestCount;
const total = this.successCount + this.failCount;
this.successRate.textContent = total > 0 ?
`${Math.round((this.successCount / total) * 100)}%` : '0%';
this.avgResponseTime.textContent = this.requestCount > 0 ?
`${Math.round(this.totalResponseTime / this.requestCount)}ms` : '0ms';
this.connectionInfo.textContent = `连接次数: ${this.requestCount}`;
}
addDataItem(data) {
if (this.dataContainer.querySelector('p')) {
this.dataContainer.innerHTML = '<h3>实时数据更新</h3>';
}
const item = document.createElement('div');
item.className = 'update-item';
const sensors = data.data || {};
const sensorHtml = Object.entries(sensors)
.map(([key, value]) => `<strong>${key}:</strong> ${value.toFixed(2)}`)
.join(' | ');
item.innerHTML = `
<div class="time">${data.timestamp}</div>
<div class="id">#${data.id} - ${data.message}</div>
<div class="data">${sensorHtml}</div>
`;
this.dataContainer.insertBefore(item, this.dataContainer.firstChild.nextSibling);
// 限制显示数量
const items = this.dataContainer.querySelectorAll('.update-item');
if (items.length > 50) {
items[items.length - 1].remove();
}
this.updateCount++;
this.updateStats();
}
async longPoll() {
if (!this.isPolling) return;
const maxUpdates = parseInt(document.getElementById('maxUpdates').value) || 0;
if (maxUpdates > 0 && this.updateCount >= maxUpdates) {
this.stopPolling();
this.setStatus('success', '已达到最大更新次数');
return;
}
this.setStatus('waiting', '等待新数据...');
try {
const startTime = Date.now();
this.requestCount++;
const response = await fetch(`${this.serverUrl}/api/poll`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const responseTime = Date.now() - startTime;
this.totalResponseTime += responseTime;
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const result = await response.json();
if (result.status === 'success' && result.data) {
this.successCount++;
this.setStatus('active', '收到新数据');
this.addDataItem(result.data);
} else if (result.status === 'timeout') {
// 超时是正常的,继续轮询
console.log('请求超时,继续轮询');
}
} catch (error) {
this.failCount++;
console.error('长轮询错误:', error);
this.showError(`请求失败: ${error.message}`);
this.setStatus('error', '连接错误');
// 错误后等待一段时间再重试
await new Promise(resolve => setTimeout(resolve, 3000));
}
// 更新统计信息
this.updateStats();
// 继续轮询
if (this.isPolling) {
this.longPoll();
}
}
startPolling() {
if (this.isPolling) return;
this.isPolling = true;
this.startTime = Date.now();
this.startBtn.disabled = true;
this.stopBtn.disabled = false;
this.setStatus('waiting', '开始轮询...');
this.longPoll();
}
stopPolling() {
this.isPolling = false;
this.startBtn.disabled = false;
this.stopBtn.disabled = true;
this.setStatus('success', '已停止轮询');
}
clearData() {
this.dataContainer.innerHTML = '<h3>实时数据更新</h3><p style="color: #999;">数据已清空,点击"开始轮询"重新获取...</p>';
this.updateCount = 0;
this.requestCount = 0;
this.successCount = 0;
this.failCount = 0;
this.totalResponseTime = 0;
this.updateStats();
}
}
// 初始化
const client = new LongPollingClient({
serverUrl: 'http://localhost:3000'
});
// 全局函数
function startPolling() {
client.startPolling();
}
function stopPolling() {
client.stopPolling();
}
function clearData() {
stopPolling();
client.clearData();
}
</script>
</body>
</html>
使用说明
安装依赖
npm init -y npm install express cors
运行服务器
node server.js
打开客户端
直接在浏览器中打开 client.html 文件
功能说明
服务器端特性:
- 模拟随机延迟 (1-8秒) 的数据更新
- 30秒超时机制
- 批量数据获取接口
- CORS跨域支持
客户端特性:
- 实时状态显示
- 统计数据跟踪 (更新次数、请求次数、成功率、平均响应时间)
- 支持设置最大更新次数
- 错误重试机制
- 数据可视化显示
- 自动清理过多数据
核心要点
长轮询工作原理:
- 客户端发起HTTP请求
- 服务器保持连接打开
- 当有新数据时立即响应
- 超时时返回空响应
- 客户端收到响应后立即发起新请求
优势:
- 实时性较好
- 实现简单
- 兼容性好
适用场景:
- 实时通知系统
- 数据监控面板
- 即时消息推送
- 在线协作工具
这个示例提供了完整的长轮询实现,包括服务器端和客户端代码,可以直接运行测试。