轮询解决方案

概述

轮询的使用场景:

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

相关推荐
mldong1 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排2 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰4 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen6 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马7 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu7 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss