在浏览器“标签页”和“窗口”之间共享WebSocket连接

原文: Sharing WebSocket Connections between Browser Tabs and Windows

前言

WebSocket 连接就像特殊的通信通道,允许 web 浏览器和服务器实时地相互通信。但是,当您在浏览器中打开多个标签页或窗口时,每个标签页或窗口都会建立自己的 WebSocket 连接。这可能会造成浪费,并影响 web 应用程序的性能。

不过别担心!在这篇博文中,我们将探索一种解决方案,当用户在不同的标签页或窗口中打开应用程序时,可以优化 WebSocket 的使用。通过在这些标签页和窗口之间共享 WebSocket 连接,我们可以使您的web应用程序更高效、更顺畅地工作。因此,让我们深入研究并发现一些有用的策略来实现浏览器标签页和窗口之间无缝的 WebSocket 连接共享。

Shared Workers

为了在浏览器标签页和窗口之间实现 WebSocket 连接共享,我们可以利用 Web worker 的力量。Web worker 本质上是独立的线程,完全独立于其他脚本,不能访问DOM。它们可以在后台执行任务,这使得它们在执行复杂的计算和防止网页冻结或无响应方面特别有用。

Shared worker 是一种特殊类型的 Web worker,可以在多个浏览器标签页或窗口之间共享。与专用于单个网页的常规 Web worker 不同,Shared worker 独立于任何特定的网页或用户界面而存在。它们允许多个浏览器上下文(如标签页或窗口)进行通信和共享资源。通过使用共享工作者,我们可以创建一个持久的实体来管理 WebSocket 连接,并促进它们在不同浏览器上下文之间的共享。而且,我们不必担心关闭连接。如果没有标签页或窗口再引用它们,共享工作线程将停止存在,因此连接将自动关闭。

Shared worker 实现 WebSocket 的连接共享

为了实现浏览器标签页和窗口之间的WebSocket连接共享,我们将遵循以下步骤:

1. 创建 Shared worker

首先使用 shared worker API创建一个 shared worker。该工作者将充当负责管理WebSocket连接的中心实体。添加一个消息处理程序来接收来自工作者的数据,并对它做任何您喜欢的事情。


// No bundler
const worker = new SharedWorker("./worker.js");

// Webpack 5
const worker = new SharedWorker(new URL("./worker.ts", import.meta.url));

worker.port.addEventListener('message', (event: MessageEvent): void => {
  // Do whatever you like with data
});
worker.port.start();

2. 建立WebSocket连接

在 shared worker 中,创建 WebSocket 对象并与服务器建立连接。此连接将在所有浏览器标签页和窗口之间共享。添加连接处理程序并在数组中存储端口。将每条接收到的消息转发给所有 MessagePort对象也很重要。


const ports: MessagePort[] = [];
const ws = new WebSocket('wss://some-url.com');

addEventListener('connect', (event: MessageEvent): void => {
  const port = event.ports[0];
  this.ports.push(port);
});

ws.addEventListener('message', (event: MessageEvent): void => {
  ports.forEach((port) => {
    port.postMessage(event.data);
  });
});

就是这样。现在,您可以在所有标签页和窗口中共享连接并同时接收消息。

3. 挑战和解决方案: 内存管理

虽然 shared worker 提供了一种高效的解决方案,但有一个挑战需要解决:内存管理。由于没有内置机制来确定 Message Port 是否仍然处于活动状态,因此引用可能会随着时间的推移而累积,从而可能导致内存泄漏。为了缓解这个问题,我们可以利用 weakref。通过使用 weakref,我们可以检查消息端口是否已被垃圾收集,从而确保有效的资源利用。


export class BrowserPort {
  private readonly weakRef: WeakRef<MessagePort>;

  constructor(port: MessagePort) {
    this.weakRef = new WeakRef(port);
    port.start();
  }

  isAlive(): boolean {
    return !!this.weakRef.deref();
  }

  postMessage(message: unknown): void {
    this.weakRef.deref()?.postMessage(message);
  }

  addEventListener(event: string, handler: (event: Event) => void): void {
    this.weakRef.deref()?.addEventListener(event, handler);
  }

  removeEventListener(event: string, handler: (event: Event) => void): void {
    this.weakRef.deref()?.removeEventListener(event, handler);
  }

  close(): void {
    this.weakRef.deref()?.close();
  }
}

let ports: BrowserPort[] = [];

addEventListener('connect', (event: MessageEvent): void => {
  const port = event.ports[0];
  ports.push(new BrowserPort(port));
});

// At some point we would like to send a message

if (port.isAlive()) {
  // It's alive! We can send a message.
  port.sendMessage('msg');
} else {
  // It's dead! We have to remove it from the array.
  ports = ports.filter(innerPort => innerPort !== port); 
}

另一个解决方案是在 onbeforeunload 事件处理程序的主体中发送一个特殊的控制消息。请记住,此方法不可靠,浏览器可能会选择忽略消息,根本不将其传递给 worker。


// Browser
window.addEventListener('onbeforeunload', (): void => {
  worker.port.sendMessage('UNLOAD');
});

// Worker
let ports: MessagePort[] = [];

addEventListener('connect', (event: MessageEvent): void => {
  const port = event.ports[0];
  ports.push(port);

  port.addEventListener('message', (event: MessageEvent): void => {
    if (event.data === 'UNLOAD') {
      ports = ports.filter(innerPort => innerPort !== port); 
    }
  });
});

总结

在这篇博文中,我们探讨了在浏览器标签页和窗口之间共享 WebSocket 连接的概念。通过利用 shared worker,我们可以在多标签页或多窗口场景中优化资源使用并增强 web 应用程序的性能。

shared worker 提供了一种强大的机制,用于建立一个管理 WebSocket 连接的中心实体,并促进不同浏览器上下文之间的通信。通过创建一个 shared worker 并利用 MessagePort API,我们可以在浏览器标签页和窗口之间无缝地传递消息,使它们能够共享一个 WebSocket 连接。

虽然需要考虑内存管理等挑战,但利用 WeakRefs 等适当的技术可以帮助减轻潜在的问题。实现合适的策略以确保有效的资源利用和防止内存泄漏非常重要。

通过实现 WebSocket 连接共享,您可以提高 web 应用程序的效率、响应能力和整体用户体验。用户可以跨多个标签页或窗口与应用程序无缝交互,同时避免不必要的资源重复。

注: 此文章为原博文的翻译内容,如有侵权,请联系删除。

原文链接:https://juejin.cn/post/7241447629875527717 作者:刃下心

(0)
上一篇 2023年6月7日 上午10:00
下一篇 2023年6月7日 上午10:10

相关推荐

发表回复

登录后才能评论