轮询解决方案

概述

轮询的使用场景:

  • 股票 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博客

相关推荐
xiaoyan20158 分钟前
最新Flutter3.32+Dart3仿微信App聊天实例
前端·flutter·dart
汪敏wangmin26 分钟前
Fiddler-抓包后直接生成Loadrunner脚本或者Jmeter脚本
前端·jmeter·fiddler
彤银浦1 小时前
Web学习笔记3
前端·笔记·学习·html5
江城开朗的豌豆1 小时前
退出登录后头像还在?这个缓存问题坑过多少前端!
前端·javascript·vue.js
江城开朗的豌豆1 小时前
Vue的'读心术':它怎么知道数据偷偷变了?
前端·javascript·vue.js
江城开朗的豌豆1 小时前
手把手教你造一个自己的v-model:原来双向绑定这么简单!
前端·javascript·vue.js
我在北京coding2 小时前
el-tree 懒加载 loadNode
前端·vue.js·elementui
江城开朗的豌豆2 小时前
v-for中key值的作用:为什么我总被要求加这个'没用的'属性?
前端·javascript·vue.js
angen20182 小时前
Ruby如何采集直播数据源地址
前端·chrome
goldenocean2 小时前
React之旅-05 List Key
前端·javascript·react.js