likes
comments
collection
share

SharedWorker 共享工作者线程实现浏览器多Tab标签页共享WS消息1、前言 刚接到的一个售前在线咨询的需求,实

作者站长头像
站长
· 阅读数 48

1、前言

刚接到的一个售前在线咨询的需求,实时聊天是通过WebSocket来实现。但是用户打开多个页面同时又在其他标签页打开咨询窗口的时候ws也会多开一个,就导致几个tab页互相抢ws连接。我就想能不能将一个页面的ws消息共享给其他页面,于是就想到了SharedWorker,并且兼容性不错(IE已死,不用怕)。

设计的想法:第一次创建的SharedWorker的时候创建websocket通信 然后通过SharedWorker来分发接收到的信息。

SharedWorker 在当前客户端下创建一个共享线程,每一个页签的通讯通过其中的port来传递和接收。而且在其他页签重新创建新的SharedWorker的时候浏览器自己会检测自己是不是已经创建过这个线程了,如果有就直接复用了。🆗,有了设想后下面先介绍一下SharedWorker的原理及如何在控制台调试SharedWorker

2、SharedWorker简介

SharedWorker 是一种浏览器 API,它允许多个浏览器上下文(如不同标签页或 iframe)共享同一个后台线程。这种特性使得 SharedWorker 成为实现跨页面通信和数据共享的理想选择。

2.1 工作原理

SharedWorker 的工作原理基于 Web Workers API,但它扩展了这一概念,允许多个页面或浏览器上下文共享同一个 Worker 脚本。这意味着,多个页面可以通过同一个 SharedWorker 实例来通信和共享数据,而不需要每个页面都创建自己的 Worker 实例。当一个页面接收到新的数据时,它可以通过 SharedWorker 将数据广播到其他页面,这样所有页面都可以实时更新其内容,而无需重新加载或轮询服务器。

2.2 创建和使用

我们可以通过new SharedWorker()来创建一个共享进程对象。创建 SharedWorker 实例时,需要指定一个 JavaScript 文件作为 Worker 脚本。这个脚本将运行在后台线程中,并且可以通过消息传递与主线程通信:

// 页面script
const mySharedWorker = new SharedWorker('sharedWorker.js');
// const mySharedWorker = new SharedWorker('sharedWorker.js', {name:'mySharedWorker'}); // 也可以指定一个name参数,即线程实例名称
mySharedWorker.port.start(); // 启动通信
mySharedWorker.port.postMessage('Hello, worker!'); // 发送消息给 Worker

在 Worker 脚本中,你可以监听来自主线程的消息,并发送响应:

// worker.js
self.onconnect = function(e) {
  var port = e.ports[0];
  port.onmessage = function(event) {
    console.log('Received message from main thread:', event.data);
    port.postMessage('Hello, main thread!');
  };
};

SharedWorker 实例标识与Worker 脚本文档源和线程名称相关。当其中Worker 脚本URL、线程名称变更时都会创建新的线程。更改名称同理,这里我就不演示了。

const mySharedWorker = new SharedWorker('sharedWorker2.js');

SharedWorker 共享工作者线程实现浏览器多Tab标签页共享WS消息1、前言 刚接到的一个售前在线咨询的需求,实

2.3 应用场景

SharedWorker工作原理可以看出其适用于需要在多个页面之间共享数据和状态的场景。例如,实时通信、游戏、协作工具等,都可以使用 SharedWorker 来实现跨页面的数据同步。

2.4 注意事项

  • 同源策略:SharedWorker 必须遵守同源策略,即所有访问 SharedWorker 的页面必须是同源的。
  • 安全性:由于 SharedWorker 可以在多个页面之间共享数据,因此需要特别注意数据的安全性和隐私性。
  • 资源管理:虽然 SharedWorker 的生命周期独立于单个页面,但需要确保在适当的时候关闭 SharedWorker,以避免资源泄漏。

2.5 调试

SharedWorker文件里面的console和debugger是不会出现在页面的控制台的,需要去专门看线程的地方查看,例如,在 Chrome 中,可以通过访问 chrome://inspect/#workers 来查看和管理当前运行的 SharedWorker 实例。

SharedWorker 共享工作者线程实现浏览器多Tab标签页共享WS消息1、前言 刚接到的一个售前在线咨询的需求,实

2.6 demo

// demo目录结构
dome
├─ page1.html
├─ page2.html
└─ sharedWorker.js

注意:SharedWorker需要服务器环境运行,demo里我使用的是vs code 插件 Live Server 安装插件后,选择其中一个html文件,在IDE右下角会出现 Go Live按钮,点击即可在浏览器打开当前页面。插件的具体用法请自行面向goggle

SharedWorker 共享工作者线程实现浏览器多Tab标签页共享WS消息1、前言 刚接到的一个售前在线咨询的需求,实

现在我们在demo文件夹下分别创建page1.html、page2.html、sharedWorker.js三个文件

<!--page1.html-->

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>Page1</title>
</head>

<body>
  <h1>Page1</h1>
  <button id="btn">click me</button>
  <script>
    const btn = document.querySelector("#btn");

    // 兼容性判断
    if (!SharedWorker) {
      throw new Error("当前浏览器不支持SharedWorker");
    }
    // 创建
    const worker = new SharedWorker("./sharedWorker.js");
    // 启动
    worker.port.start();
    // 线程监听消息
    worker.port.onmessage = (e) => {
      console.log("page1共享计数值:", e.data);
      const p = document.createElement("p");
      p.textContent = `page1共享计数值:${e.data}`;
      document.body.appendChild(p);
    };
    btn.addEventListener("click", () => {
      worker.port.postMessage("+");
    });
  </script>
</body>

</html>
<!--page2.html-->
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>Page2</title>
</head>

<body>
  <h1>Page2</h1>
  <button id="btn">click me</button>
  <script>
    const btn = document.querySelector("#btn");

    // 兼容性判断
    if (!SharedWorker) {
      throw new Error("当前浏览器不支持SharedWorker");
    }
    // 创建
    const worker = new SharedWorker("./sharedWorker.js");
    // 启动
    worker.port.start();
    // 线程监听消息
    worker.port.onmessage = (e) => {
      console.log("page1共享计数值:", e.data);
      const p = document.createElement("p");
      p.textContent = `page2共享计数值:${e.data}`;
      document.body.appendChild(p);
    };
    btn.addEventListener("click", () => {
      worker.port.postMessage("+");
    });
  </script>
</body>

</html>
// sharedWorker.js
let count = 0;
const ports = []; // 存储所有连接的端口

// 连接函数 每次创建都会调用这个函数
self.onconnect = (e) => {
  // 获取端口
  const port = e.ports[0];
  console.log("SharedWorker 连接成功", port);
  ports.push(port);
  // 监听方法
  port.onmessage = (msg) => {
    // 这边的console.log在页面是看不到的 需要在线程里面看
    console.log("共享线程接收到信息:", msg.data, count);
    if (msg.data === "+") {
      count++;
    }
    // 循环向所有端口广播
    ports.forEach((port) => {
      port.postMessage(count);
    });
  };

  port.ondisconnect = function() {
    const index = ports.indexOf(port);
    if (index > -1) {
      ports.splice(index, 1); // 移除断开连接的端口
    }
  };
};

🆗,代码写完我们在chrome打开页面和线程控制台,如下图,则表示sharedWorker连接成功:

SharedWorker 共享工作者线程实现浏览器多Tab标签页共享WS消息1、前言 刚接到的一个售前在线咨询的需求,实

当点击page1和page2页面上的按钮,我们会看到消息通过port.postMessage广播给了所有页面,从而实现共享:

SharedWorker 共享工作者线程实现浏览器多Tab标签页共享WS消息1、前言 刚接到的一个售前在线咨询的需求,实 注意: 为了确保所有标签页共享同一个 SharedWorker 实例,这些页面必须同源,并且worker URL和name参数都一样(可以省略 name 参数),这样所有使用该路径的 SharedWorker 实例都将是同一个实例。

🆗,了解了 SharedWorker的工作原理后我们回到主题,也就是在具体的项目开发中实现共享WebSocket连接

3、在nuxt项目中使用 SharedWorker 实现共享WS消息

先看效果:

SharedWorker 共享工作者线程实现浏览器多Tab标签页共享WS消息1、前言 刚接到的一个售前在线咨询的需求,实

注意: sharedWorker.js 不要放在utils文件夹下,否则编译之后 SharedWorker 无法解析脚本路径,可以将sharedWorker.js文件放到静态文件夹下,我这里是public,具体看你项目的配置。

我们先封装一下sharedWorker.js,并写好webSocket连接,下面代码里写了很多注释,基本没有难度:

sharedWorker.js

let ws;
let heartbeatInterval;
let reconnectionInterval;
const heartbeatTime = 30 * 1000
const reconnectionTime = 3000

// 存储所有连接的端口
let ports = [];

self.onconnect = function(e) {
  let port = e.ports[0];
  ports.push(port); // 添加新连接的端口
  console.log("SharedWorker 连接成功", port);
  port.onmessage = function(event) {
    console.log("SharedWorker 收到消息",event.data, event.data.url);
    if (event.data.url) {
      connect(event.data.url); // 尝试连接 WebSocket
    }
  };

  port.ondisconnect = function() {
    const index = ports.indexOf(port);
    if (index > -1) {
      ports.splice(index, 1); // 移除断开连接的端口
    }
  };

  // ws连接函数
  function connect(url) {
    if (ws) {
      ws.close();
    }

    ws = new WebSocket(url);
    ws.onopen = function() {
      clearTimeout(reconnectionInterval); // 清除重连定时器
      heartbeat(); // 开始心跳
      port.postMessage({ status: 'open' });
    };

    ws.onmessage = function(event) {
      ports.forEach((port) => { // 广播消息给所有连接的端口
        port.postMessage({ status: 'message', data: event.data });
      });
    };

    ws.onclose = function(event) {
      console.log('WebSocket close:', event);

      clearInterval(heartbeatInterval); // 停止心跳
      port.postMessage({ status: 'close', event: event });

      // 尝试重连
      reconnectionInterval = setTimeout(function() {
        connect(url);
      }, reconnectionTime); // 3秒后重连
    };

    ws.onerror = function(event) {
      console.log('WebSocket error:', event);
      port.postMessage({ status: 'error', event: event });
    };
  }

  // 心跳检测函数
  function heartbeat() {
    heartbeatInterval = setInterval(function() {
      if (ws.readyState === WebSocket.OPEN) {
        const ping = JSON.stringify({ actionType: "0" })
        ws.send(ping); // 发送心跳包
      } else {
        clearInterval(heartbeatInterval); // 如果连接未打开,停止心跳
      }
    }, heartbeatTime); // 每30秒发送一次心跳
  }
};

接下来,我们在vue文件中创建SharedWorker实例即可。

index.vue

const initSharedWorker = () => {
  getWebSocketUrl()
    .then((wsUrl) => {
      // 创建 SharedWorker 实例
      let myWorker = new SharedWorker("/sharedWorker.js");

      // 监听 SharedWorker 发送回来的消息
      myWorker.port.addEventListener("message", function (event) {
        switch (event.data.status) {
          case "open":
            console.log("WebSocket 已打开");
            break;

          case "message":
            const msg = JSON.parse(event.data.data);
            if (msg.actionType == 1) wsMsg.value = msg.body;
            console.log("WebSocket 接收到消息", msg, typeof msg);
            break;
            
          case "close":
            console.log("WebSocket 已关闭");
            break;

          case "error":
            console.error("WebSocket 发生错误:", event.data);
            break;
        }
      });

      // 启动 SharedWorker
      myWorker.port.start();
      // 通过端口向 SharedWorker 发送 WebSocket URL
      myWorker.port.postMessage({ url: wsUrl });
    })
    .catch((error) => {
      console.error(error);
    });
};

🆗,关于SharedWorker应用的分享就到这了~ 这是我摆烂近两年后的第一篇博客👻,希望能帮到有需要的同学。

💕看完两件事:

  1. 点赞 | 你可以点击——>收藏——>退出一气呵成,但别忘了点赞🤭
  2. 关注 | 点个关注,下次不迷路😘
转载自:https://juejin.cn/post/7413705154754347058
评论
请登录