长轮询模拟实时数据获取

wen java案例 2

本文目录导读:

长轮询模拟实时数据获取

  1. 服务器端代码 (Node.js + Express)
  2. 客户端代码 (HTML + JavaScript)
  3. 使用说明

我来为您创建一个完整的长轮询模拟实时数据获取的示例代码,这个示例将模拟服务器端和客户端的长轮询通信。

服务器端代码 (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跨域支持

客户端特性:

  • 实时状态显示
  • 统计数据跟踪 (更新次数、请求次数、成功率、平均响应时间)
  • 支持设置最大更新次数
  • 错误重试机制
  • 数据可视化显示
  • 自动清理过多数据

核心要点

长轮询工作原理:

  1. 客户端发起HTTP请求
  2. 服务器保持连接打开
  3. 当有新数据时立即响应
  4. 超时时返回空响应
  5. 客户端收到响应后立即发起新请求

优势:

  • 实时性较好
  • 实现简单
  • 兼容性好

适用场景:

  • 实时通知系统
  • 数据监控面板
  • 即时消息推送
  • 在线协作工具

这个示例提供了完整的长轮询实现,包括服务器端和客户端代码,可以直接运行测试。

抱歉,评论功能暂时关闭!