实时更新:轮询、SSE 和 Web 套接字

2025-05-28

实时更新:轮询、SSE 和 Web 套接字

封面图片由Unsplash上的Martin Jaros提供。

嘿👋

如果你是一名初级或中级前端开发人员👨‍💻,你很可能已经实现了一些需要实时更新的功能。比如通知系统、聊天应用、上传进度条或社交媒体状态指示器。

开发一个聊天应用是每个人的待办事项之一。我知道这一点,因为我也经历过。我按照 YouTube 教程做了一个。🤷‍♂️ 几乎所​​有 YouTube 频道都上传了同样的教程:使用socket.io

憨豆先生抄袭

你知道 socket.io 底层使用了 Web Socket 吗?是的,你可能知道。但是,只有 Web Socket 才能实现实时更新吗?🤨 不是。这不是唯一的方法。本文还会介绍其他几种方法。👇

我们将介绍三种技术:

  1. 轮询 + 长轮询
  2. 服务器发送事件
  3. 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);
Enter fullscreen mode Exit fullscreen mode

长轮询

既然谈到这个话题,就有必要聊聊长轮询。长轮询是轮询的天才/优化版本。

服务器不会立即发送响应,而是等到有新的数据发送给客户端。客户端渴望响应;这实际上没有问题,因为客户端不会被阻塞,可以继续执行其他任务。需要注意的是,这也需要服务器端做一些工作。

长轮询图解

一旦客户端收到数据,它就必须为下一状态的数据创建另一个请求。

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();

Enter fullscreen mode Exit fullscreen mode

注意:这些代码片段仅提供了最基本的功能,用于表达想法。您可能想添加更多功能,例如尝试次数计数或延迟。最好在代码中添加一些检查,这样您就不会最终对自己的服务器发起 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();
});

Enter fullscreen mode Exit fullscreen mode

这是可能的最短的实现。

  1. 我们创建了一条GET路线/real-time-updates
  2. Content-Type标题设置为text/event-stream
  3. 用于res.write()向客户端发送数据。如果使用res.send()或 ,res.end()它将关闭连接。

👉需要注意的要点

  1. 消息应始终以data:. 开头,也可以是空格。
  2. 消息应始终以 结尾\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!');

Enter fullscreen mode Exit fullscreen mode

我们使用EventSource接口与 SSE 端点建立连接。

  1. 使用 获取客户端实例EventSource。传递您要订阅的 URL。
  2. 我们得到 3 个被称为不同阶段的事件处理程序。
    • onopen当连接打开时被调用。
    • onerror当发生错误时被调用。
    • onmessage当我们从服务器接收到事件并且我们没有明确处理该事件时被调用。
  3. 我们还获得了一种close可用于随时关闭连接的方法。

如果我们没有在服务器上指定事件类型,默认情况下,每个事件都具有类型message。因此,处理程序onmessage会捕获每个事件。

但是如果我们使用关键字指定一个事件,event:我们就可以在客户端上明确地处理它。

// diff: server-side code with custom event

res.write("event: notification\ndata: New data!\n\n");
Enter fullscreen mode Exit fullscreen mode
// diff: client-side code with custom event handling

sseClient.addEventListener('notification', (event) => {
    console.log(event.data))
};
Enter fullscreen mode Exit fullscreen mode

这就是添加您自己的 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 层。

Web 套接字插图

HTTP 协议是无状态协议,这意味着所有标头(包括 Cookie、Token 等)都会随每个请求一起发送。这使得它具有水平可扩展性。如果服务器 1 过载,请求可以由服务器 2 处理,因为我们在标头中拥有所有信息,所以这不会造成任何影响。这也会导致速度变慢,因为每个请求都需要发送更多数据。此外,请求完成后连接就会关闭。因此,对于新的请求,必须重新打开连接,这非常耗时。

另一方面,TCP 是有状态的。Web Socket 速度更快,因为连接在通信过程中保持活动状态,并且每个请求无需发送额外的标头。但这也使其扩展性略有下降。如果客户端正在与服务器 1 通信,那么所有请求都应该仅由服务器 1 处理。其他服务器无法感知它的状态。


读完这篇文章后的你

话虽如此,没有完美的解决方案。根据具体情况,可能有一个比另一个更好,但了解所有可能的替代方案总是好的。💪

💡 目前正在开发一种基于 HTTP/3 的新 API,称为WebTransport,旨在实现低延迟、双向、多路复用、客户端-服务器消息传递。

📌 保存以供日后使用。

好了,各位!感谢阅读。🙏


希望你喜欢这篇文章。如果你想看前端开发相关的内容,可以在LinkedInTwitter上联系我。

🕊

文章来源:https://dev.to/thesanjeevsharma/real-time-updates-polling-sse-and-web-sockets-277i
PREV
我如何选择编程语言并克服坏习惯
NEXT
Just Redux:完整指南