实时更新数据的几种方式
背景
在我们的日常工作中,我们往往会遇到客户端需要实时获取服务端最新数据的场景,例如聊天系统(WeChat/Telegram),股票行情查看软件(同花顺/富途),feed 推送系统(Twitter/微博)等等。在实现这些需求的时候,我们的技术方案是有很多的,本文将会给大家介绍四种常见的实时获取服务端数据的方案,它们分别是:短轮询(polling),长轮询(long polling),长连接(WebSocket)以及服务器事件推送(Sever-Sent Events, aka SSE)。本篇文章将会介绍每种方案的基本原理,以及分别使用他们来实现同一个需求:动态事件列表,我们用到的技术栈是原生 html + 原生 NodeJS。
需求介绍
先说一下这个动态事件列表的需求:我们的服务器每隔 5 秒会产生一个新的事件,每个事件都有一个 id 字段以及 timestamp 字段,id 和 timestamp 字段都是该事件生成的时间戳,前端会以列表的形式展示目前服务端已产生的所有事件信息,后面当服务器产生新的事件时,前端会获取到最新的事件并添加到页面列表的末尾。
一、轮询
概念解释
我相信大多数程序员或多或少都使用过轮询来获取服务端的资源,简单来说轮询就是客户端不停地调用服务端接口以获得最新的数据。客户端在发起请求后服务端会立即响应,不过因为这时服务端的数据没有更新所以返回了一个空的结果给客户端。客户端在等待了一段时间后(可能是几秒),再次请求服务端的数据,这时由于服务端的数据发生了更新,所以会给客户端返回最新的数据,客户端在拿到数据后等待一下然后继续发送请求,如此反复。
代码实现
下面就让我们用轮询来实现动态事件列表的需求, 首先是 Node 代码:
//服务端代码实现
const http = require('http');
const url = require('url');
// 事件列表
const events = []
// 最新生成的事件时间
let latestTimestamp = 0
// 事件生产者
const EventProducer = () => {
const event = {
id: Date.now(),
timestamp: Date.now()
}
events.push(event)
latestTimestamp = event.timestamp
}
// 每隔5秒生成一个新的事件
setInterval(() => {
EventProducer()
}, 5000)
//创建服务
const server = http.createServer((req, resp) => {
const urlParsed = url.parse(req.url, true)
resp.setHeader('Access-Control-Allow-Origin', '*')
resp.setHeader('Origin', '*')
if (urlParsed.pathname == '/events') {
// 每次客户端都带上它最后拿到的事件时间戳来获取新的事件
const timestamp = parseInt(urlParsed.query['timestamp'])
// 判断客户端是否拿到最新事件
if (timestamp < latestTimestamp) {
// 将所有没发送过给这个客户端的事件一次性发送出去
resp.write(JSON.stringify(events.filter(event => event.timestamp > timestamp)))
}
resp.end()
}
})
server.listen(8080, () => {
console.log('server is listening')
})
上面的代码十分简单,我们实现了一个 eventsAPI,前端每次都会带上上一次的时间戳来请求这个时间点后的最新事件。接着再来看一下前端的实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端实时更新数据实现</title>
</head>
<body>
<div id="wrap"></div>
</body>
<script>
//前端存储事件列表
const events = [];
//最新的数据时间戳
let timestamp = 0;
//获取数据函数
const fetchLatestEvents = async (timestamp) => {
// 获取最新的事件
const body = await window.fetch(`http://localhost:8080/events?timestamp=${timestamp}`);
if (body.ok) {
const json = await body.json()
return json
} else {
console.error('failed to fetch')
}
}
//设置轮询,3秒发一次请求
function App() {
const timer = setInterval(async () => {
const latestEvents = await fetchLatestEvents(timestamp);
if (latestEvents && latestEvents.length) {
timestamp = latestEvents[latestEvents.length - 1].timestamp;
events.push(...latestEvents);
renderEl(events);
}
}, 3000)
}
//把数据渲染到页面上
function renderEl(arr) {
let str = '';
版权归原作者 勒布朗-前端 所有, 如有侵权,请联系我们删除。