实时更新:轮询、SSE 和 Web 套接字
封面图片由Unsplash上的Martin Jaros提供。
嘿👋
如果你是一名初级或中级前端开发人员👨💻,你很可能已经实现了一些需要实时更新的功能。比如通知系统、聊天应用、上传进度条或社交媒体状态指示器。
开发一个聊天应用是每个人的待办事项之一。我知道这一点,因为我也经历过。我按照 YouTube 教程做了一个。🤷♂️ 几乎所有 YouTube 频道都上传了同样的教程:使用socket.io。
你知道 socket.io 底层使用了 Web Socket 吗?是的,你可能知道。但是,只有 Web Socket 才能实现实时更新吗?🤨 不是。这不是唯一的方法。本文还会介绍其他几种方法。👇
我们将介绍三种技术:
- 轮询 + 长轮询
- 服务器发送事件
- Web Sockets(简介)
我尽力用插图来解释这些。🎨
轮询
这是构建实时应用程序时最简单的方法。
在轮询中,客户端会反复向服务器发出请求,希望获得更新/新数据。无需额外步骤即可实现。只需用 包装您的 API 调用setInterval
即可。通俗地说,这就像每隔几秒钟刷新一次网页一样。
您可能收到更新的数据,也可能没有。我们无法提前知道这一点。
const URL = 'https://jsonplaceholder.typicode.com/posts/';
const fetchPosts = async () => {
try {
console.log('Fetching new data...');
const response = await (await fetch(URL)).json();
console.log('Data fetched!')
} catch(err) {
console.log('Request failed: ', err.message);
}
}
setInterval(fetchPosts, 5000);
长轮询
既然谈到这个话题,就有必要聊聊长轮询。长轮询是轮询的天才/优化版本。
服务器不会立即发送响应,而是等到有新的数据发送给客户端。客户端渴望响应;这实际上没有问题,因为客户端不会被阻塞,可以继续执行其他任务。需要注意的是,这也需要服务器端做一些工作。
一旦客户端收到数据,它就必须为下一状态的数据创建另一个请求。
const URL = "https://jsonplaceholder.typicode.com/posts";
const fetchPosts = async () => {
try {
console.log("Fetching new data...");
const response = await (await fetch(URL)).json();
console.log("Data fetched!");
return response;
} catch (err) {
console.log("Request failed: ", err.message);
}
};
const longPoll = async () => {
// response might be delayed as server might not have updated data
const response = await fetchPosts();
if (response) {
return longPoll();
}
}
longPoll();
注意:这些代码片段仅提供了最基本的功能,用于表达想法。您可能想添加更多功能,例如尝试次数计数或延迟。最好在代码中添加一些检查,这样您就不会最终对自己的服务器发起 DOS 攻击。💩
服务器发送事件
这是这篇文章我最喜欢的部分。我最近在Syfe工作时了解了 SSE (我们正在招聘!)。在此之前,我只了解 Web Socket 并使用它,即使是在小型应用中也是如此。SSE 功能强大、简单易用,并且只需极少的代码即可完成工作。👌
在 SSE 中,客户端会向服务器发出初始请求以建立连接。之后,服务器会在数据可用时将更新的数据推送给客户端。客户端无需进一步参与。当然,客户端需要处理这些事件,但仅此而已。
// server-side code in express
app.get("/real-time-updates", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
const sendRealTimeUpdates = () => {
res.write("data: New data!\n\n");
setTimeout(sendRealTimeUpdates, 3000);
};
sendRealTimeUpdates();
});
这是可能的最短的实现。
- 我们创建了一条
GET
路线/real-time-updates
。 - 将
Content-Type
标题设置为text/event-stream
。 - 用于
res.write()
向客户端发送数据。如果使用res.send()
或 ,res.end()
它将关闭连接。
👉需要注意的要点
- 消息应始终以
data:
. 开头,也可以是空格。 - 消息应始终以 结尾
\n\n
。
res.write
我们通过包装来模拟实时更新setTimeout
。
// client-side code in vanilla JS
const URL = 'http://127.0.0.1:3000/real-time-updates';
const sseClient = new EventSource(URL);
sseClient.onopen = () => console.log('Connection opened!');
sseClient.onmessage = (event) => console.log(event.data);
sseClient.onerror = () => console.log('Something went wrong!');
我们使用EventSource
接口与 SSE 端点建立连接。
- 使用 获取客户端实例
EventSource
。传递您要订阅的 URL。 - 我们得到 3 个被称为不同阶段的事件处理程序。
onopen
当连接打开时被调用。onerror
当发生错误时被调用。onmessage
当我们从服务器接收到事件并且我们没有明确处理该事件时被调用。
- 我们还获得了一种
close
可用于随时关闭连接的方法。
如果我们没有在服务器上指定事件类型,默认情况下,每个事件都具有类型message
。因此,处理程序onmessage
会捕获每个事件。
但是如果我们使用关键字指定一个事件,event:
我们就可以在客户端上明确地处理它。
// diff: server-side code with custom event
res.write("event: notification\ndata: New data!\n\n");
// diff: client-side code with custom event handling
sseClient.addEventListener('notification', (event) => {
console.log(event.data))
};
这就是添加您自己的 SSE 所需的全部代码。🤓
⚠️ 当 SSE 基于 HTTP/1.1 实现时,其最大连接数限制为 6。这意味着任何网站www.fake-dev.to
在浏览器中最多只能打开 6 个 SSE 连接(包括多个标签页)。建议使用 HTTP/2,其默认限制为 100,但可以配置。
Web套接字
Web Sockets 比上述方法更强大,但也带来了额外的复杂性。
Web Sockets 形成双工连接,这意味着客户端和服务器可以在单个通道上相互发送数据,而 SSE 是单向的。
Web Sockets 由 HTTP 请求发起握手,但后来升级到 TCP 层。
HTTP 协议是无状态协议,这意味着所有标头(包括 Cookie、Token 等)都会随每个请求一起发送。这使得它具有水平可扩展性。如果服务器 1 过载,请求可以由服务器 2 处理,因为我们在标头中拥有所有信息,所以这不会造成任何影响。这也会导致速度变慢,因为每个请求都需要发送更多数据。此外,请求完成后连接就会关闭。因此,对于新的请求,必须重新打开连接,这非常耗时。
另一方面,TCP 是有状态的。Web Socket 速度更快,因为连接在通信过程中保持活动状态,并且每个请求无需发送额外的标头。但这也使其扩展性略有下降。如果客户端正在与服务器 1 通信,那么所有请求都应该仅由服务器 1 处理。其他服务器无法感知它的状态。
话虽如此,没有完美的解决方案。根据具体情况,可能有一个比另一个更好,但了解所有可能的替代方案总是好的。💪
💡 目前正在开发一种基于 HTTP/3 的新 API,称为WebTransport,旨在实现低延迟、双向、多路复用、客户端-服务器消息传递。
📌 保存以供日后使用。
好了,各位!感谢阅读。🙏
希望你喜欢这篇文章。如果你想看前端开发相关的内容,可以在LinkedIn或Twitter上联系我。
🕊
文章来源:https://dev.to/thesanjeevsharma/real-time-updates-polling-sse-and-web-sockets-277i