vue-使用Worker实现多标签页共享一个WebSocket

文章目录

  • 前言
  • 一、SharedWorker 是什么
    • SharedWorker 是什么
    • SharedWorker 的使用方式
    • SharedWorker 标识与独占
  • 二、Demo使用
  • 三、使用SharedWorker实现WebSocket共享


前言

最近有一个需求,需要实现用户系统消息时时提醒功能。第一时间就是想用WebSocket进行长连接。但是前端项目点击跳转需要打开新的标签页。这个时间就会出现新的标签页打开会把老的WebSocket连接挤掉。然后就想到了去共享一个WebSocket连接。就能实现多个标签页消息共享了。

一、SharedWorker 是什么

SharedWorker 是什么

SharedWorker 是一种特殊类型的 Worker,可以被多个浏览上下文访问,比如多个 windows,iframes 和 workers,但这些浏览上下文必须同源。它们实现于一个不同于普通 worker 的接口,具有不同的全局作用域:SharedWorkerGlobalScope ,但是继承自WorkerGlobalScope

SharedWorker 的使用方式

SharedWorker 线程的创建和使用跟 worker 类似,事件和方法也基本一样。 不同点在于,主线程与 SharedWorker 线程是通过MessagePort建立起链接,数据通讯方法都挂载在SharedWorker.port上。

值得注意的是,如果你采用 addEventListener 来接收 message 事件,那么在主线程初始化SharedWorker()后,还要调用 SharedWorker.port.start() 方法来手动开启端口。

// main.js(主线程)
const myWorker = new SharedWorker('./sharedWorker.js');

myWorker.port.start(); // 开启端口

myWorker.port.addEventListener('message', msg => {
    console.log(msg.data);
})


但是,如果采用 onmessage 方法,则默认开启端口,不需要再手动调用SharedWorker.port.start()方法

// main.js(主线程)
const myWorker = new SharedWorker('./sharedWorker.js');

myWorker.port.onmessage = msg => {
    console.log(msg.data);
};


SharedWorker 标识与独占

共享工作者线程标识源自解析后的脚本 URL、工作者线程名称和文档源。(可以通过第二参数给SharedWorker 命名

实例化一个共享工作者线程 
如果你的服务地址正好就是xxx.com那么这三种解析方式就是同一个线程,只会创建一个,类似同源策略
另外两个会在其原有线程上增加一个端口port(需要我们通过创建一个ports数组存起来,方便之后数据分发)
- 全部基于同源调用构造函数
- 所有脚本解析为相同的 URL 
- 所有线程都有相同的名称
new SharedWorker('./sharedWorker.js'); 
new SharedWorker('sharedWorker.js'); 
new SharedWorker('https://xxx.com/sharedWorker.js');

如果当其中URL、工作者线程名称和文档源变更时候都会创建新的线程。

  • 改变url这个好理解
  • 改变文档源
demo中我又创建了一个page3.html
和另一个SharedWorker2.js
// 创建
page3与page1中唯一不同的就是引用了SharedWorker2.js
const worker = new SharedWorker("./SharedWorker2.js");

在这里插入图片描述

改变名字

demo中我又创建了一个page4.html
// 创建
page4和page2中唯一不同的就是给了不同的第2个名字(两种写法,效果相同,只不过对象还能传递其他参数)
page2中(直接给字符串)
 const worker = new SharedWorker("./SharedWorker.js",'page2');
page4中(给了对象)
 const worker = new SharedWorker("./SharedWorker.js",{name:'page4'});

在这里插入图片描述

二、Demo使用

demo演示:
在这里插入图片描述
demo条件

  • 需要服务器环境运行。我这边使用的是vs code 插件Live Server(这玩意咋用自己百度下)可以看一下视频里面的地址是127开头的。
  • chrome浏览器(这个不用多说了)要提一点的是SharedWorker 文件里面的console和debugger是不会出现page1 和page2的控制台的,这个需要去专门看线程的地方查看。chrome浏览器通过chrome://inspect/#workers进入。看图:
  • 在这里插入图片描述

上代码
SharedWorker.js

// 记个数
let count = 0;
// 把每个连接的端口存下来
const ports = [];

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

page1.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>SharedWorker-page1</title>
  </head>
  <body>
    <h1>SharedWorker-page1</h1>
    <button id="btn">count++</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);
      };
      btn.addEventListener("click", (_) => {
        worker.port.postMessage("+");
      });
    </script>
  </body>
</html>

page2.hrml

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>SharedWorker-page2</title>
  </head>
  <body>
    <h1>SharedWorker-page2</h1>
    <button id="btn">count++</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("page2共享线程计数值:", e.data);
      };
      btn.addEventListener("click", (_) => {
        worker.port.postMessage("+");
      });
    </script>
  </body>
</html>

上面的代码基本上就已经算是OK了。

三、使用SharedWorker实现WebSocket共享

SharedWorker.js
SharedWorker的js文件是需要让各个浏览器页签引用的。所以将文件放在了public中

// 记个数
let count = 0;
// 把每个连接的端口存下来
const ports = [];
var state = {
    webSocket: null, // webSocket实例
    lockReconnect: false, // 重连锁,避免多次重连
    maxReconnect: 6, // 最大重连次数, -1 标识无限重连
    reconnectTime: 0, // 重连尝试次数
    heartbeat: {
        interval: 30 * 1000, // 心跳间隔时间
        timeout: 10 * 1000, // 响应超时时间
        pingTimeoutObj: null, // 延时发送心跳的定时器
        pongTimeoutObj: null, // 接收心跳响应的定时器
        pingMessage: JSON.stringify({type: 'ping'}), // 心跳请求信息
    },
    token:null
}


// 连接函数 每次创建都会调用这个函数
onconnect = (e) => {
    console.log("这里是共享线程展示位置", e);
    // 获取端口
    const port = e.ports[0];
    // 把丫存起来
    ports.push(port);
    // 监听方法
    port.onmessage = (msg) => {
        // 这边的console.log是看不到的 debugger也是看不到的 需要在线程里面看
        console.log("共享线程接收到信息:", msg);
        var data = msg.data || {}
        var conf = JSON.parse(data)
        console.log("解析后的参数", conf)
        switch (conf.type) {
            case "open":
                console.log("共享线程状态为Open")
                if (!state.webSocket) {
                    state.token=conf.token
                    initWebSocket(conf.host, conf.baseURL, conf.uri, state.token, conf.tenant);
                }
                break
            case 'portClose':
                console.log("共享线程状态为portClose")
                // 关闭当前端口(new SharedWorker 会默认开启端口)
                if (ports.indexOf(port) > -1) {
                    ports.splice(ports.indexOf(port), 1)
                }
                break
            case 'wsClose':
                // 关闭websocket
                console.log("共享线程状态为WsClose")
                state.webSocket.close();
                clearTimeoutObj(state.heartbeat);
                state.websocket = null
                state.token=null
                break
            case 'close':
                // 关闭SharedWorker 通过self调用 SharedWorkerGlobalScope 的实例
                console.log("共享线程状态为close")
                self.close()
                break
            default:
                break

        }
    };
};

const initWebSocket = (host, baseURL, uri, token, tenant) => {
    // ws地址
    let wsUri = `ws://${host}${baseURL}${uri}?access_token=${token}&TENANT-ID=${tenant}`;
    // let wsUri = `ws://${host}${baseURL}${other.adaptationUrl(props.uri)}?access_token=${token.value}&TENANT-ID=${tenant.value}`;

    // let wsUri = `ws://${host}${baseURL}${uri}?access_token=${token}`;
    // 建立连接
    state.webSocket = new WebSocket(wsUri);

    // 连接成功
    state.webSocket.onopen = onOpen;
    // 连接错误
    state.webSocket.onerror = onError;
    // 接收信息
    state.webSocket.onmessage = onMessage;
    // 连接关闭
    state.webSocket.onclose = onClose;
};

const reconnect = () => {
    if (!state.token) {
        return;
    }
    if (state.lockReconnect || (state.maxReconnect !== -1 && state.reconnectTime > state.maxReconnect)) {
        return;
    }
    state.lockReconnect = true;
    setTimeout(() => {
        state.reconnectTime++;
        // 建立新连接
        initWebSocket();
        state.lockReconnect = false;
    }, 5000);
};
/**
 * 清空定时器
 */
const clearTimeoutObj = (heartbeat) => {
    heartbeat.pingTimeoutObj && clearTimeout(heartbeat.pingTimeoutObj);
    heartbeat.pongTimeoutObj && clearTimeout(heartbeat.pongTimeoutObj);
};
/**
 * 开启心跳
 */
const startHeartbeat = () => {
    const webSocket = state.webSocket;
    const heartbeat = state.heartbeat;
    // 清空定时器
    clearTimeoutObj(heartbeat);
    // 延时发送下一次心跳
    heartbeat.pingTimeoutObj = setTimeout(() => {
        // 如果连接正常
        if (webSocket.readyState === 1) {
            //这里发送一个心跳,后端收到后,返回一个心跳消息,
            webSocket.send(heartbeat.pingMessage);
            // 心跳发送后,如果服务器超时未响应则断开,如果响应了会被重置心跳定时器
            heartbeat.pongTimeoutObj = setTimeout(() => {
                webSocket.close();
            }, heartbeat.timeout);
        } else {
            // 否则重连
            reconnect();
        }
    }, heartbeat.interval);
};

/**
 * 连接成功事件
 */
const onOpen = () => {
    console.log("连接成功")
    //开启心跳
    startHeartbeat();
    state.reconnectTime = 0;
};
/**
 * 连接失败事件
 * @param e
 */
const onError = () => {
    console.log("连接 失败")
    //重连
    reconnect();
};

/**
 * 连接关闭事件
 * @param e
 */
const onClose = () => {
    //重连
    reconnect();
};
/**
 * 接收服务器推送的信息
 * @param msgEvent
 */
const onMessage = (msgEvent) => {
    //收到服务器信息,心跳重置并发送
    console.log("接到消息", msgEvent)
    startHeartbeat();
    // const text = JSON.parse(msgEvent.data);
    ports.forEach((p) => {
        p.postMessage(msgEvent.data);
    });
};

定义一个组件叫WebSocket.vue

代码中有一些token的判断可以无视。
我这里怎么简单怎么来。定义一个组件直接放到app.vue中引用(主打的就是一个方便)
我这里接收到消息后使用mitt.js进行各消息分发

<template>
	<div></div>
</template>
<script setup lang="ts" name="global-websocket">
import { Session } from '@/utils/storage';
import {computed, onMounted, onUnmounted, ref,watch} from "vue";
import {eventBus} from "@/utils/eventBus"
import other from "@/utils/other";

const props = defineProps({
	uri: {
		type: String,
	},
});
const isLogin=ref<any>()
const worker=ref()
const token = computed(() => {
	return Session.getToken();
});

const tenant = computed(() => {
	return Session.getTenant();
});
watch(isLogin,(newValue, oldValue) =>{
  if(newValue){
    initWebSocket();
  }
})
onMounted(() => {
	// initWebSocket();
  if(sessionStorage.getItem('token')){
    initWebSocket();
  }else{
    window.addEventListener('setItem', () => {
      isLogin.value = sessionStorage.getItem('token')
    });
  }
});

onUnmounted(() => {
  let conf={
    type:"wsClose",
  }
  worker.value.port.postMessage(JSON.stringify(conf))
});

const initWebSocket = () => {
  if (!SharedWorker) {
    throw new Error("当前浏览器不支持SharedWorker");
  }
// 创建
  worker.value = new SharedWorker("../../../public/SharedWorker.js");

// 线程监听消息
  worker.value.port.onmessage = (e:any) => {
    console.log("接受到消息:", e.data);
    sendEventBus(JSON.parse(e.data))
  };
  let conf={
    type:"open",
    host:window.location.host,
    baseURL:import.meta.env.VITE_API_URL,
    uri:other.adaptationUrl(props.uri),
    token:token.value,
    tenant:tenant.value
  }
  worker.value.port.postMessage(JSON.stringify(conf))
};
const sendEventBus=(text:any)=>{
  switch (text.type){
      case "pong":
          return;
      case "discuss":
          eventBus.emit('discuss', text);
          break;
      case "onlineusers":
          eventBus.emit('onlineusers', text);
          break;
      case "livestart":
          eventBus.emit('livestart', text);
          break;
      case "message_notify":
          eventBus.emit('message_notify', text);
          break;
  }
}
</script>

mitt消息总线的使用

npm install --save mitt

// eventBus.ts
import createEventBus from 'mitt';

export const eventBus = createEventBus();

使用

import {eventBus} from "@/utils/eventBus"

//发送消息
eventBus.emit('discuss', text);

//监听消息
eventBus.on('discuss', (data) => {
    console.log(data)
  });

本文借鉴:https://blog.csdn.net/jinke0010/article/details/124248321

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/782767.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

14-47 剑和诗人21 - 2024年如何打造AI创业公司

​​​​​ 2024 年&#xff0c;随着人工智能继续快速发展并融入几乎所有行业&#xff0c;创建一家人工智能初创公司将带来巨大的机遇。然而&#xff0c;在吸引资金、招聘人才、开发专有技术以及将产品推向市场方面&#xff0c;人工智能初创公司也面临着相当大的挑战。 让我来…

下一代 CLI 工具,使用Go语言用于构建令人惊叹的网络应用程序

大家好&#xff0c;今天给大家分享一个创新的命令行工具Gowebly CLI&#xff0c;它专注于使用Go语言来快速构建现代Web应用程序。 Gowebly CLI 是一款免费开源软件&#xff0c;有助于在后端使用 Go、在前端使用 htmx 和 hyperscript 以及最流行的 CSS 框架轻松构建令人惊叹的 W…

Maven Nexus3 私服搭建、配置、项目发布指南

maven nexus私服搭建 访问nexus3官方镜像库,选择需要的版本下载:Docker Nexus docker pull sonatype/nexus3:3.49.0 创建数据目录并赋权 sudo mkdir /nexus-data && sudo chown -R 200 /nexus-data 运行(数据目录选择硬盘大的卷进行挂载) docker run -d -p 808…

AI集成工具平台一站式体验,零门槛使用国内外主流大模型

目录 0 写在前面1 AI艺术大师1.1 绘画制图1.2 智能作曲 2 AI科研助理2.1 学术搜索2.2 自动代码 3 AI智能对话3.1 聊天机器人3.2 模型竞技场 4 特别福利 0 写在前面 人工智能大模型浪潮滚滚&#xff0c;正推动着千行百业的数智化进程。随着技术演进&#xff0c;2024年被视为是大…

数据库开发:mysql基础一

文章目录 数据库开发Day15&#xff1a;MySQL基础&#xff08;一&#xff09;一、MySQL介绍与安装【1】MySQL介绍&#xff08;5&#xff09;启动MySQL服务&#xff08;6&#xff09;修改root登陆密码 二、SQL简介三、数据库操作四、数据表操作4.1、数据库数据类型4.2、创建数据表…

tomcat原理、结构、设计模式

1 what 一种web服务器&#xff0c;运行java servlet、jsp技术&#xff0c;能为java web提供运行环境并通过http协议处理客户端请求。即tomcat http服务器 servlet容器。同类产品有jetty Web应用&#xff1a;Web应用是指通过Web浏览器访问的应用程序&#xff0c;它使用Web技术…

c#类型转换和常见集合类型

目录 1. 整数转换&#xff0c;整数和字符串&#xff0c;字符串和整数之间的转换怎么实现&#xff1f; 2. 日期转换&#xff0c;获取当前日期&#xff0c;字符串转日期&#xff0c;日期转字符串怎么实现&#xff1f; 3. 举例一维、二维、三维数组 4. 需求&#xff1a;有个88…

【嵌入式单片机】之RS-232、RS-485、RS-422比较

1. RS422是什么 RS422,正式名称为TIA/EIA-422,是一种串行通信标准,专为实现长距离、高可靠性的数据传输而设计。它采用差分信号传输技术,通过两对双绞线实现全双工通信,即发送和接收可以同时进行。RS422在工业自动化和远程监控系统中曾经扮演着重要角色,以其出色的抗干扰…

p标签文本段落中因编辑器换行引起的空格问题完美解决方案

目录 1.修改前的代码&#xff1a;2.修改后的代码3.总结 在HTML文档中&#xff0c;如何要在&#xff08;p标签&#xff09;内写一段很长的文本段落&#xff0c;并且没有 换行。由于IDE或者编辑器界面大小有限或需要在vue中逻辑处理动态显示文本&#xff0c;一行写完太长&#x…

14-46 剑和诗人20 – 减少幻觉的提示词工程

​​​​​ 概述 幻觉或“编造”是大型语言模型 (LLM) 的常见故障模式&#xff0c;它们会产生事实上不正确或无意义的内容。幻觉背后的一些主要原因是&#xff1a; 当模型不确定真正的答案时&#xff0c;它会试图通过捏造信息来提供过度的帮助。该模型缺乏适当的基础、背景和…

YOLOv5、v7、v8如何修改检测框文字颜色和大小

YOLOv5和YOLOv8默认的标签文字颜色为白色&#xff0c;但是在亮度较大的图片中文字不明显&#xff0c;就需要对标签文字的颜色进行修改 一、YOLOv5 打开X:\Anaconda\envs\your-env\Lib\site-packages\ultralytics\utils\plotting.py X代表你的anaconda安装的盘&#xff0c;yo…

格蠹汇编阅读理解

一、调试工具使用方式 WinDbg常用命令&#xff1a; 执行 lm 命令&#xff0c;可以看到进程中有几个模块。执行~命令列一下线程。用!heap 命令列一下堆。执行!address 命令可以列出用户态空间中的所有区域。搜索吧&#xff01;就从当前进程用户态空间的较低地址开始搜&#xf…

基于大数据技术Hadoop的气象分析可视化大屏设计和实现

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

Android - 手势

Android 提供特殊类型的触摸屏事件&#xff0c;例如捏合、双击、滚动、长按和退缩。 这些都被称为手势。 Android 提供了 GestureDetector 类来接收运动事件并告诉我们这些事件是否对应手势。 要使用它&#xff0c;您需要创建一个 GestureDetector 对象&#xff0c;然后使用 Ge…

SpringBoot源码阅读(1)——环境搭建

SpringBoot官网 官网 https://spring.io/projects/spring-boot 代码仓库 github&#xff1a;https://github.com/spring-projects/spring-boot gitee: https://gitee.com/mirrors/spring-boot 下载代码 git clone https://gitee.com/mirrors/spring-boot.git下载的代码中有些…

如何看自己电脑的ip地址?这些方法教你搞定

在数字化时代&#xff0c;网络已经成为我们生活中不可或缺的一部分。对于每一个接入网络的设备来说&#xff0c;IP地址就像是一个独特的身份证&#xff0c;它标识着设备在网络中的位置。对于电脑用户而言&#xff0c;了解如何查看自己电脑的IP地址&#xff0c;不仅有助于我们更…

满足信创环境运行的国产FTP服务器是什么样的?

2018 年以来&#xff0c;受“华为、中兴事件”影响&#xff0c;我国科技尤其是上游核心技术受制于人的现状对我 国经济发展提出了严峻考验。在全球产业从工业经济向数字经济升级的关键时期&#xff0c;中国明确 “数字中国”建设战略&#xff0c; 抢占数字经济产业链制高点。 在…

【Python】已解决:(paddleocr导包报错)ModuleNotFoundError: No module named ‘paddle’

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;&#xff08;paddleocr导包报错&#xff09;ModuleNotFoundError: No module named ‘paddle’ 一、分析问题背景 近日&#xff0c;一些使用PaddleOCR库进行文字…

西安电子科技大学833、834学长经验分享(初复试总成绩第一、机试第二)

考研经验分享 首先自我介绍。2024考研上岸西安电子科技大学计算机学硕&#xff0c;初试成绩 390 分&#xff0c;复试成绩第一&#xff0c;初复试总成绩第一&#xff0c;机试第二&#xff0c;跟着研梦全程班上岸。 成绩单&#xff1a; 本文主要分为以下几个部分&#xff1a;【…

可视化作品集(09):可视化运维大屏不可或缺。

可视化大屏在可视化运维上有很多价值&#xff0c;而且应用十分普遍&#xff0c;本文给老铁们分享一下。 1. 实时监控&#xff1a;可视化大屏可以实时展示系统运行状态、设备状态、生产数据等信息&#xff0c;使运维人员能够及时发现问题并做出相应的处理。 2. 数据分析&#x…