在本文中,我们将介绍三种技巧/技术来实现实时更新功能:
- 轮询+长轮询
- SSE(服务器发送事件)
- Web Sockets
轮询
这是构建实时应用程序时最简单的方法。
在轮询中,客户端反复向服务器发出请求,希望获得更新/新的数据。不需要额外的步骤来实现这一点。只需将您的 API 调用包装起来即可setInterval
。通俗地说,这就像每次几秒钟后刷新你的网页。
也许您会收到更新的数据,也可能不会。没有办法事先知道这一点。
javascript
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);
长轮询
既然我们正在讨论这个主题,那么值得在这里讨论一下长轮询。长轮询是轮询的天才/优化版本。
服务器不会立即发送响应,而是等待,直到为客户端提供一些新数据。客户渴望得到回应;这实际上很好,因为客户端没有被阻止并继续执行其他任务。据了解,这也需要在服务器端做出一些努力。
一旦客户端收到数据,它必须为下一个数据状态创建另一个请求。
javascript
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 操作。
SSE 服务器发送事件
这是本文中我最喜欢的部分。在此之前,我只了解网络套接字并使用它们,即使对于小型应用程序也是如此。SSE 功能强大、简单,并且可以用最少的代码完成工作。
在SSE中,客户端向服务器发出初始请求以建立连接。发布服务器将更新的数据推送到客户端(只要有可用)。客户无需进一步参与。当然,客户端需要处理这些事件,但仅此而已。
javascript
// 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()
orres.end()
它将关闭连接。
👉重要注意事项
- 该消息应始终以 开头
data:
。 - 该消息应始终以 结尾
\n\n
。
我们通过res.write
用setTimeout
.
ini
// 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:
我们可以在客户端显式处理它。
swift
// diff: server-side code with custom event
res.write("event: notification\ndata: New data!\n\n");
javascript
// 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 Sockets
Web Sockets 比上述方法更强大,但也带来了额外的复杂性。
Web Sockets 形成双工连接,这意味着客户端和服务器都可以在单个通道上相互发送数据,而 SSE 是单向的。
Web Sockets 由 HTTP 握手请求发起,但后来升级到 TCP 层。
HTTP 协议是无状态协议,这意味着所有标头(包括 cookie、令牌等)都会随每个请求一起发送。这使得它可以水平扩展。如果服务器 1 过载,请求可以由服务器 2 处理,并且由于我们在标头中拥有所有信息,因此没有什么区别。这也使得速度变慢,因为每个请求都需要发送更多数据。此外,一旦请求得到满足,连接就会关闭。因此,对于新请求,必须再次打开连接,这非常耗时。
另一方面,TCP 是有状态的。Web 套接字速度更快,因为连接保持活动状态以进行通信,并且每个请求都不会发送额外的标头。但这也使得扩展变得更加困难。如果客户端正在与服务器 1 通信,则所有请求应仅由服务器 1 处理。其他服务器不知道它的状态。