写点什么

实时消息获取技术方案对比

作者:南城FE
  • 2024-05-20
    广东
  • 本文字数:6155 字

    阅读完需:约 20 分钟

本文翻译自 WebSockets vs Server-Sent-Events vs Long-Polling vs WebRTC vs WebTransport,略有删改。 来源:rxdb.info


对于现代 Web 应用,从服务器向客户端发送事件的能力是必不可少的。这种必要性导致了几种方法的发展,每种方法都有自己的优点和缺点。最开始长轮询是唯一可用的选项。后来WebSockets取得了不错的效果,它为双向通信提供了一个更强大的解决方案。继WebSockets之后,服务器发送事件(SSE)为从服务器到客户端的单向通信提供了一种更简单的方法。展望未来,WebTransport协议有望通过提供更高效、灵活和可扩展的方法来进一步彻底改变这一局面。适用于小众场景WebRTC也可以考虑用于服务器往客户端发送事件。


本文通过研究这些技术,比较它们的性能,突出它们的优点和局限性,并使用各种用例提供建议,以帮助开发人员在构建实时 Web 应用程序时做出明智的决策。

什么是长轮询?

长轮询是第一个支持服务器->客户端传递消息的方法,该方法可用于 HTTP 浏览器。该技术模拟服务器推送通信与正常的 XHR 请求。与传统轮询不同,在传统轮询中,客户端以固定的时间间隔重复地向服务器请求数据,长轮询建立到服务器的连接,该连接保持打开状态,直到有新的数据可用。一旦服务器有了新的信息,它就将响应发送给客户端,连接就关闭了。在收到服务器的响应后,客户端立即发起新的请求,然后重复该过程。这种方法可以更及时地更新数据,减少不必要的网络流量和服务器负载。但是它仍然会在通信中引入延迟,并且比WebSockets等其他实时技术效率低。


// JavaScript 客户端中的长轮询function longPoll() {    fetch('http://example.com/poll')        .then(response => response.json())        .then(data => {            console.log("Received data:", data);            longPoll(); // 立即建立新的长轮询请求        })        .catch(error => {            /**             * 在正常情况下,当连接超时或客户端离线时,             * 可能会出现错误。连接超时或客户端离线。             * 出现错误时会在延迟一段时间后重新开始轮询。             */            setTimeout(longPoll, 10000);        });}longPoll(); // 启动长轮询
复制代码


如上面的代码所示,在客户端实现长轮询非常简单。但在后端要确保客户端接收到所有事件,并且在客户端正在重新连接时不会错过更新,可能会遇到很多困难。

什么是 WebSockets?

WebSockets 通过客户端和服务器之间的单个长期连接提供全双工通信通道。这项技术使浏览器和服务器能够交换数据,而不需要 HTTP 请求-响应周期的开销,从而促进了实时聊天、游戏或金融交易平台等应用的实时数据传输。WebSockets 允许双方在建立连接后独立发送数据,是传统 HTTP 的一大进步,非常适合需要低延迟和高频率更新的应用场景。


// JavaScript 客户端中的 WebSocketconst socket = new WebSocket('ws://example.com');
socket.onopen = function(event) { console.log('Connection established'); // 向服务器发送信息 socket.send('Hello Server!');};
socket.onmessage = function(event) { console.log('Message from server:', event.data);};
复制代码


虽然WebSocket API 的基础使用很容易,但在生产中却相当复杂。可能会断开连接,因此必须相应地重新创建。特别是检测连接是否仍然可用,可能非常棘手。大多数情况下,您需要添加一个心跳动作,以确保打开的连接不会关闭。这种复杂性正是大多数人在 WebSockets 上使用 Socket.IO 等库的原因,这些库可以处理所有这些情况,甚至在需要时提供长时间轮询的功能。

什么是服务器发送事件?

服务器发送事件(SSE)提供了一种通过 HTTP 将服务器更新推送到客户端的标准方法。与WebSockets不同,SSE 专为从服务器到客户端的单向通信而设计,使其非常适合实时新闻提要,体育比分或客户端需要真实的更新而无需向服务器发送数据的情况。


你可以把服务器发送事件想象成一次单独的 HTTP 请求,后端不会一次性发送整个主体内容,而是保持连接开启状态,并在每次需要将事件发送到客户端时发送单行信息作为结果。


使用 SSE 创建用于接收事件的连接非常简单。在浏览器的客户端上,使用生成事件的服务器端脚本的 URL 初始化 EventSource 实例。


监听消息涉及将事件处理程序直接附加到 EventSource 实例。API 对通用消息事件和命名事件进行了区分,从而允许进行更结构化的通信。下面是 JavaScript 中的设置方法:


// 连接服务器端事件流const evtSource = new EventSource("https://example.com/events");
// 处理事件回调evtSource.onmessage = event => { console.log('got message: ' + event.data);};
复制代码


WebSockets不同,EventSource 会在连接丢失时自动重新连接。


在服务器端,您的脚本必须将Content-Type头设置为text/event-stream,并根据 SSE 规范格式化每条消息。这包括指定事件类型、数据负载和可选字段(如事件 ID 和重试时间)。


以下是如何在 Node.js Express 应用中设置简单的 SSE:


import express from 'express';const app = express();const PORT = process.env.PORT || 3000;
app.get('/events', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', });
const sendEvent = (data) => { // 所有消息必须以data: 作为前缀 const formattedData = `data: ${JSON.stringify(data)}\n\n`; res.write(formattedData); };
// 每 2 秒发送一次事件 const intervalId = setInterval(() => { const message = { time: new Date().toTimeString(), message: 'Hello from the server!', }; sendEvent(message); }, 2000);
// 连接关闭时清理定时器 req.on('close', () => { clearInterval(intervalId); res.end(); });});app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
复制代码

什么是 WebTransport API?

WebTransport 是一种先进的应用程序接口,旨在实现网络客户端与服务器之间高效、低延迟的通信。它利用 HTTP/3 QUIC 协议实现了多种数据传输功能,如通过多个数据流以可靠或不可靠的方式发送数据,甚至允许不按顺序发送数据。这使得 WebTransport 成为需要高性能网络的应用(如实时游戏、实时流媒体和协作平台)的强大工具。不过值得注意的是,WebTransport 目前只是一个工作草案,尚未得到广泛采用。截至目前(2024 年 3 月),WebTransport 仍处于工作草案阶段,尚未得到广泛支持。您还不能在 Safari 浏览器中使用 WebTransportNode.js 中也没有原生支持。这限制了它在不同平台和环境中的可用性。


即使WebTransport将得到广泛支持,它的 API 使用起来也非常复杂,后期很可能会在WebTransport之上构建库,而不是直接在应用程序的源代码中使用它。

什么是 WebRTC?

WebRTC(Web Real-Time Communication)是一个开源项目和 API 标准,它可以直接在 Web 浏览器和移动的应用程序中实现实时通信(RTC)功能,而无需复杂的服务器基础设施或安装额外的插件。它支持点对点连接,用于音频,视频和浏览器之间的数据交换。WebRTC旨在通过 NAT 和防火墙工作,利用 ICE,STUN 和 TURN 等协议在对等体之间建立连接。


虽然WebRTC被用于客户端—客户端交互,但它也可以用于服务器—客户端通信,其中服务器只是模拟为客户端。这种方法只对利基用例有意义,这就是为什么在下面的WebRTC将被忽略作为一个选项。


问题是要让WebRTC工作,无论如何都需要一个信令服务器,然后它将再次运行在websocketsSSEWebTransport上。这违背了使用WebRTC作为这些技术的替代品的目的。

技术的局限性

双向发送数据

只有WebSocketsWebTransport允许双向发送数据,这样你就可以在同一个连接上接收服务器数据和发送客户端数据。


虽然理论上也可以使用长轮询(Long-Polling)功能,但不建议这样做,因为向现有的长轮询连接发送 "新 "数据无论如何都需要进行额外的 http 请求。你可以通过额外的 http 请求直接从客户端向服务器发送数据,而不必中断长轮询连接。


服务器发送事件不支持向服务器发送任何其他数据。您只能执行初始请求,即使在初始请求中,也不能使用本机 EventSource API 在 http 主体中发送类似 POST 的数据。相反你必须把所有的数据都放在 url 参数中,这被认为是一个不好的安全实践,因为凭证可能会泄露到服务器日志,代理和缓存中。

每个域名的请求数限制

大多数现代浏览器允许每个域有六个连接,这限制了所有稳定的服务器到客户端消息传递方法的可用性。六个连接的限制甚至在浏览器选项卡之间共享,因此当您在多个选项卡中打开同一页面时,它们必须彼此共享六个连接池。这个限制是 HTTP/1.1-RFC 的一部分(它甚至定义了一个更低的数量,只有两个连接)。


引用自 RFC 2616 单用户客户端不应该与任何服务器或代理保持超过 2 个连接。一个代理应该使用最多 2*N 个连接到另一个服务器或代理,其中 N 是同时活动的用户数。这些准则旨在改善 HTTP 响应时间并避免拥塞。“


虽然该策略可以防止网站所有者使用他们的访问者访问 D-DOS 其他网站,但当需要多个连接来处理合法用例的服务器-客户端通信时,这可能是一个大问题。要解决这个限制,您必须使用 HTTP/2 或 HTTP/3,浏览器将只打开每个域的单个连接,然后使用多路复用来通过单个连接运行所有数据。虽然这实际上为您提供了无限数量的并行连接,但有一个SETTINGS_MAX_CONCURRENT_STREAMS设置可以限制实际连接数量。对于大多数配置,默认值为 100 个并发流。

连接在移动的应用程序上不保持打开状态

在 Android 和 iOS 等操作系统上运行的移动的应用程序的上下文中,维护开放连接(例如用于 WebSockets 和其他连接的连接)是一项重大挑战。移动的操作系统被设计为在一段时间不活动后自动将应用程序移到后台,有效地关闭任何打开的连接。此行为是操作系统资源管理策略的一部分,用于节省电池电量和优化性能。因此开发人员通常依赖于移动的推送通知作为从服务器向客户端发送数据的有效且可靠的方法。推送通知允许服务器提醒应用程序新数据,提示操作或更新,而无需持久打开连接。

性能比较

比较WebSockets、服务器发送事件(SSE)、长轮询和WebTransport的性能涉及评估各种条件下的延迟、吞吐量、服务器负载和可扩展性等关键方面。


首先让我们看看原始数据。在这个仓库中(github.com/Sh3b0/realtime-web?tab=readme-ov-file#demos)可以找到一个很好的性能比较,它测试了 Go Lang 服务器实现中的消息时间。在这里我们可以看到WebSocketsWebRTCWebTransport的性能相当:



WebTransport 是一种基于 HTTP/3 协议的新技术。未来(2024 年 3 月之后)可能会有更多的性能优化。此外 WebTransport 经过优化以使用更少的功率,该指标未经测试。


让我们也比较一下延迟,吞吐量和可扩展性:

延迟

  • WebSockets:由于其通过单个持久连接进行全双工通信,因此提供最低的延迟。非常适合需要即时数据交换的实时应用。

  • 服务器发送的事件:为服务器到客户端的通信提供了低延迟,但如果没有额外的 HTTP 请求,则无法将消息发送回服务器。

  • 长轮询:导致更高的延迟,因为它依赖于为每次数据传输建立新的 HTTP 连接,使得实时更新的效率较低。也可能发生服务器想要在客户端仍在打开新连接的过程中发送事件的情况。在这些情况下,延迟将显著更大。

  • WebTransport:承诺提供类似于 WebSockets 的低延迟,并利用 HTTP/3 协议实现更有效的多路复用和拥塞控制。

吞吐量

  • WebSockets:由于其持久连接而具有高吞吐量,但吞吐量可能会受到背压的影响,客户端无法像服务器能够发送的那样快地处理数据。

  • 服务器发送的事件:高效地将消息广播到许多客户端,开销比 WebSockets 少,从而可能提高单向服务器到客户端通信的吞吐量。

  • 长轮询:通常提供较低的吞吐量,因为频繁打开和关闭连接的开销会消耗更多的服务器资源。

  • WebTransport:预期在单个连接中支持单向和双向流的高吞吐量,在需要多个流的情况下优于 WebSocket。

可扩展性和服务器负载

  • WebSockets:维护大量的 WebSocket 连接会显著增加服务器负载,从而可能影响具有许多用户的应用程序的可伸缩性。

  • 服务器发送的事件:对于主要需要从服务器到客户端更新的场景更具可扩展性,因为它使用的连接开销比 WebSockets 少,因为它使用“普通”HTTP 请求,而不需要使用 WebSockets 运行协议更新。

  • 长轮询:由于频繁的连接建立会产生很高的服务器负载,因此可伸缩性最差,仅适合作为后备机制。

  • WebTransport:设计为高度可扩展,受益于 HTTP/3 处理连接和流的效率,与 WebSockets 和 SSE 相比,可能会减少服务器负载。

建议和案例

在服务器-客户端通信技术领域,每种技术都有其独特的优势和用例适用性。服务器发送事件(SSE)是最直接的实现选项,它利用与传统 Web 请求相同的 HTTP/S 协议,从而规避了企业防火墙限制和其他协议可能出现的其他技术问题。它们很容易集成到 Node.js 和其他服务器框架中,使其成为需要频繁更新服务器到客户端的应用程序的理想选择,例如新闻提要,股票报价和实时事件流。


WebSockets在需要持续双向通信的场景中表现出色。它们支持持续交互的能力使其成为浏览器游戏、聊天应用程序和实时体育更新的首选。


WebTransport尽管有潜力,但仍面临着采用方面的挑战。它不被包括 Node.js 在内的服务器框架广泛支持,并且缺乏与 Safari 的兼容性。此外,它对 HTTP/3 的依赖进一步限制了它的直接适用性,因为许多像 nginx 这样的 Web 服务器只支持实验性的 HTTP/3。虽然WebTransport支持可靠和不可靠的数据传输,对未来的应用程序很有希望,但对于大多数用例来说,它还不是一个可行的选择。


长轮询曾经是一种常见的技术,现在由于其效率低下和重复建立新 HTTP 连接的高开销而在很大程度上已经过时。虽然它可以作为缺乏对 WebSockets 或 SSE 支持的环境的后备,但由于显著的性能限制,通常不鼓励使用它。

已知问题

对于所有的实时流技术,存在已知的问题。当你基于它们实现业务功能时,请记住这些。

客户端在重新连接时可能会错过事件

当客户端正在连接、重新连接或离线时,可能会错过服务器上发生但无法流式传输到客户端的事件。当服务器每次都在流式传输完整内容(如实时更新的股票行情)时,这种漏掉的事件是无关紧要的。但当后端要对部分结果进行流式处理时,就必须考虑漏报事件。如果在后端解决这个问题,规模会非常大,因为后端必须记住每个客户端已经成功发送了哪些事件。相反这应该通过客户端逻辑来实现。

公司防火墙可能造成问题

在使用任何流媒体技术时,公司基础设施都存在许多已知问题。代理和防火墙可能会阻止流量或无意中破坏请求和响应。无论何时在这样的基础设施中实施实时应用程序,都要确保首先测试该技术本身是否适合自己。

总结

本文探讨了现代实时 Web 应用程序中服务器向客户端发送事件的不同技术,包括长轮询、WebSockets、服务器发送事件(Server-Sent Events, SSE)、WebTransport 和 WebRTC。文章比较了这些技术的性能,突出了它们的优势和限制,并为不同的用例提供了建议,帮助开发者在构建实时 Web 应用程序时做出决策和建议。




看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~


专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)

发布于: 刚刚阅读数: 4
用户头像

南城FE

关注

公众号@南城大前端 2019-02-12 加入

专注前端开发,分享前端知识

评论

发布
暂无评论
实时消息获取技术方案对比_前端_南城FE_InfoQ写作社区