概述
轮询的使用场景:
- 股票 K 线图
- 聊天
- 重要通知,实时预警
这些场景都是都要实时性的。
http 是请求响应模式,一定需要先请求,后响应。
解决方案:
- 短轮询:interval 定时发送请求。问题:大量无意义的请求(老舔狗了😭),频繁打开关闭连接,而且定时很难确定固定的时间。
- 长轮询:发送请求,直接有响应后,才发送下一次请求。问题:客户端长时间没有响应,导致超时,断开 TCP 连接(解决方法是当超时了立即再发送一次请求 - 抛出异常再发请求);而且服务器没有响应的时候挂起请求也需要占用服务器资源。
- Websocket:WebSocket也是建立在TCP协议之上的,利用的是TCP全双工通信的能力,使用WebSocket,会经历两个阶段:握手阶段、通信阶段。缺点也有:维持 tcp 连接需要耗费资源。
示例
一、WebSocket + 轮询 回退机制
1. 后端(Node.js 使用 ws
库实现 WebSocket)
安装依赖:
npm install express ws
创建 server.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
:
<!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
:
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();
};
启动步骤:
- 在终端运行
node server.js
。 - 访问
http://localhost:3000/
,页面会首先尝试通过 WebSocket 获取数据,10s 之后, WebSocket 连接断开则自动回退到轮询方式获取数据,然后可以看到不断向后端接口发送请求进行轮训。
二、SSE + 轮询 回退机制
1. 后端(Node.js 使用原生 EventSource
实现 SSE)
安装依赖:
npm install express
创建 server.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
:
<!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
:
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();
};
启动步骤:
- 在终端运行
node server.js
。 - 访问
http://localhost:3000/
,页面会首先尝试通过 SSE 获取数据,若 SSE 连接断开则自动回退到轮询方式获取数据。
贴一篇写的不错的文章:技术方案实践: 前端轮询方案实现 & 思考_前端轮询方式的实现-CSDN博客