轮询解决方案

概述

轮询的使用场景:

  • 股票 K 线图
  • 聊天
  • 重要通知,实时预警

这些场景都是都要实时性的。

http 是请求响应模式,一定需要先请求,后响应。

解决方案:

  • 短轮询:interval 定时发送请求。问题:大量无意义的请求(老舔狗了😭),频繁打开关闭连接,而且定时很难确定固定的时间。
  • 长轮询:发送请求,直接有响应后,才发送下一次请求。问题:客户端长时间没有响应,导致超时,断开 TCP 连接(解决方法是当超时了立即再发送一次请求 - 抛出异常再发请求);而且服务器没有响应的时候挂起请求也需要占用服务器资源。
  • Websocket:WebSocket也是建立在TCP协议之上的,利用的是TCP全双工通信的能力,使用WebSocket,会经历两个阶段:握手阶段、通信阶段。缺点也有:维持 tcp 连接需要耗费资源。

示例

一、WebSocket + 轮询 回退机制

1. 后端(Node.js 使用 ws 库实现 WebSocket)

安装依赖:

bash 复制代码
npm install express ws

创建 server.js

js 复制代码
const express = require('express');
const WebSocket = require('ws');

const app = express();
const port = 3000;

// 使用 HTTP 服务作为静态文件服务器
app.use(express.static('public'));

// 轮询 API 模拟
app.get('/api/data', (req, res) => {
  const data = { timestamp: Date.now(), message: 'Polling data' };
  res.json(data);
});

// 创建 WebSocket 服务
const server = app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

const wss = new WebSocket.Server({ server });

// WebSocket 连接处理
wss.on('connection', (ws) => {
  console.log('WebSocket client connected');

  // 模拟定时推送数据
  const intervalId = setInterval(() => {
    const data = { timestamp: Date.now(), message: 'WebSocket data' };
    ws.send(JSON.stringify(data));
  }, 2000);
  
  //10s 后断开连接
  setTimeout(() => {
      ws.close();
  }, 10000);

  ws.on('close', () => {
    console.log('WebSocket client disconnected');
    clearInterval(intervalId);
  });
});
2. 前端代码

创建 public/index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebSocket + Polling</title>
  <script src="app.js" defer></script>
</head>
<body>
  <h1>WebSocket + Polling 回退机制</h1>
  <div id="output"></div>
</body>
</html>

创建 public/app.js

js 复制代码
let websocket;
let pollingInterval = 5000; // 初始轮询间隔为5秒
let pollingTimeout;

const outputDiv = document.getElementById('output');

// 显示数据
function displayData(data) {
  const dataElement = document.createElement('p');
  dataElement.textContent = `时间戳: ${data.timestamp}, 消息: ${data.message}`;
  outputDiv.appendChild(dataElement);
}

// 初始化 WebSocket 连接
function connectWebSocket() {
  websocket = new WebSocket('ws://localhost:3000');

  websocket.onopen = function() {
    console.log('WebSocket 连接成功');
    clearTimeout(pollingTimeout); // WebSocket 成功连接后停止轮询
  };

  websocket.onmessage = function(event) {
    const data = JSON.parse(event.data);
    console.log('接收到 WebSocket 数据:', data);
    displayData(data);
  };

  websocket.onclose = function() {
    console.log('WebSocket 连接断开,启动轮询');
    startPolling();
  };

  websocket.onerror = function(error) {
    console.error('WebSocket 错误:', error);
    websocket.close();
  };
}

// 轮询函数
function startPolling() {
  fetch('/api/data')
    .then(response => response.json())
    .then(data => {
      console.log('轮询获取的数据:', data);
      displayData(data);
    })
    .catch(error => {
      console.error('轮询错误:', error);
    })
    .finally(() => {
      // 动态调整轮询频率(可选择)
      pollingTimeout = setTimeout(startPolling, pollingInterval);
      pollingInterval = Math.min(pollingInterval * 2, 60000); // 最长间隔不超过60秒
    });
}

// 页面加载时尝试建立 WebSocket 连接
window.onload = function() {
  connectWebSocket();
};
启动步骤:
  1. 在终端运行 node server.js
  2. 访问 http://localhost:3000/,页面会首先尝试通过 WebSocket 获取数据,10s 之后, WebSocket 连接断开则自动回退到轮询方式获取数据,然后可以看到不断向后端接口发送请求进行轮训。

二、SSE + 轮询 回退机制

1. 后端(Node.js 使用原生 EventSource 实现 SSE)

安装依赖:

bash 复制代码
npm install express

创建 server.js

js 复制代码
const express = require('express');
const app = express();
const port = 3000;

// 使用 HTTP 服务作为静态文件服务器
app.use(express.static('public'));

// 轮询 API 模拟
app.get('/api/data', (req, res) => {
    const data = { timestamp: Date.now(), message: 'Polling data' };
    res.json(data);
});

// SSE 接口
app.get('/api/sse', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    // 解决跨域
    res.setHeader('Access-Control-Allow-Origin', '*');

    const sendSSE = () => {
        const data = { timestamp: Date.now(), message: 'SSE data' };
        res.write(`data: ${JSON.stringify(data)}`);
    };

    const intervalId = setInterval(sendSSE, 2000);

    // 10s 后停止发送数据 关闭SSE连接
    setTimeout(() => {
        clearInterval(intervalId);
        res.end();
    }, 10000);

    // 当客户端断开连接时清除定时器
    req.on('close', () => {
        clearInterval(intervalId);
    });
});

app.listen(port, () => {
    console.log(`Server listening on port ${port}`);
});
2. 前端代码

创建 public/index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SSE + Polling</title>
  <script src="app.js" defer></script>
</head>
<body>
  <h1>SSE + Polling 回退机制</h1>
  <div id="output"></div>
</body>
</html>

创建 public/app.js

js 复制代码
let eventSource;
let pollingInterval = 5000; // 初始轮询间隔为5秒
let pollingTimeout;

const outputDiv = document.getElementById('output');

// 显示数据
function displayData(data) {
    const dataElement = document.createElement('p');
    dataElement.textContent = `时间戳: ${data.timestamp}, 消息: ${data.message}`;
    outputDiv.appendChild(dataElement);
}

// 初始化 SSE 连接
function connectSSE() {
    eventSource = new EventSource('http://localhost:3000/api/sse');

    eventSource.onopen = function() {
        console.log('SSE 连接成功');
        clearTimeout(pollingTimeout); // SSE 成功连接后停止轮询
    };

    eventSource.onmessage = function(event) {
        const data = JSON.parse(event.data);
        console.log('接收到 SSE 数据:', data);
        displayData(data);
    };

    eventSource.onerror = function(error) {
        console.error('SSE 错误:', error);
        eventSource.close();
        startPolling(); // SSE 断开后启动轮询
    };
}

// 轮询函数
function startPolling() {
    fetch('/api/data')
        .then(response => response.json())
        .then(data => {
            console.log('轮询获取的数据:', data);
            displayData(data);
        })
        .catch(error => {
            console.error('轮询错误:', error);
        })
        .finally(() => {
            // 动态调整轮询频率
            pollingTimeout = setTimeout(startPolling, pollingInterval);
            pollingInterval = Math.min(pollingInterval * 2, 60000); // 最长间隔不超过60秒
        });
}

// 页面加载时尝试建立 SSE 连接
window.onload = function() {
    connectSSE();
};
启动步骤:
  1. 在终端运行 node server.js
  2. 访问 http://localhost:3000/,页面会首先尝试通过 SSE 获取数据,若 SSE 连接断开则自动回退到轮询方式获取数据。

贴一篇写的不错的文章:技术方案实践: 前端轮询方案实现 & 思考_前端轮询方式的实现-CSDN博客

相关推荐
覆水难收呀几秒前
三、(JS)JS中常见的表单事件
开发语言·前端·javascript
猿来如此呀8 分钟前
运行npm install 时,卡在sill idealTree buildDeps没有反应
前端·npm·node.js
hw_happy13 分钟前
解决 npm ERR! node-sass 和 gyp ERR! node-gyp 报错问题
前端·npm·sass
FHKHH18 分钟前
计算机网络第二章:作业 1: Web 服务器
服务器·前端·计算机网络
视觉小鸟1 小时前
【JVM安装MinIO】
前端·jvm·chrome
二川bro1 小时前
【已解决】Uncaught RangeError: Maximum depth reached
前端
qq22951165022 小时前
python毕业设计基于django+vue医院社区医疗挂号预约综合管理系统7918h-pycharm-flask
前端·vue.js·express
WebGIS皮卡茂3 小时前
【数据可视化】Arcgis api4.x 热力图、时间动态热力图、timeSlider时间滑块控件应用 (超详细、附免费教学数据、收藏!)
javascript·vue.js·arcgis·信息可视化
八了个戒3 小时前
Koa (下一代web框架) 【Node.js进阶】
前端·node.js
Sca_杰3 小时前
vue2使用npm引入依赖(例如axios),报错Module parse failed: Unexpected token解决方案
前端·javascript·vue