uniapp自定义websocket类实现socket通信、心跳检测、连接检测、重连机制

uniapp自定义websocket类实现socket通信、心跳检测、检测连接、重连机制,仿vue-socket插件功能实现发送序列号进行连接检测,发送消息时42【key,value】格式,根据后端返回数据和需要接收到的数据做nsend与onSocketMessage的修改

import {
	publish,
	subscribe,
	unsubscribe
} from 'pubsub-js'
import {
	isArray
} from "@/utils/validate"

const noop = function() {};
class Socket {
	//心跳检测定时器
	static stopTime = 0;
	//每25s发送报文给服务端监听连接是否正常
	static sendInterval = 0;
	//服务端监听连接异常时提示并进行socket重连
	static isCommunication = false;
	//连接中出错调用心跳检测方法的定时器
	static errorNr = '';
	//websocket已连接数
	static concatCount = 0;
	//连接后连接设备次数(用于拼接传输序号42后的数字)
	static sendTag = 0
	//pc端emit发送消息有回调函数时加入
	static isCallback = ["leave", "join"]
	//连接或断连publish相应isCallback数组内的值给客户端响应
	static sendKey = "";

	//new Socket时执行构造方法,对参数默认值进行初始化
	constructor({
		url = '',
		onOpen = noop,
		onMsg = noop,
		onClose = noop,
		onError = noop,
		onReload = noop,
		onRdFinsh = noop,
		maxInterValCount = 10,
		interValTime = 2000,
		...args
	} = {}) {
		this.isRconnectIng = false; //是否处于重连状态
		this.waiting = Promise.resolve(false); //心跳检查必须等待重连完成后
		this.waitDep = []; //等待时收集依赖的容器

		this.SocketTask = {
			nsend: noop,
			hbDetection: noop,
			nclose: noop,
			nrconnect: noop,
			isconnect: false,
			uniColse: false,
			maxInterValCount,
			interValTime,
			InterValCount: 0,
			eventPatch: null,
			url,
			onOpen,
			onMsg,
			onClose,
			onError,
			onReload,
			onRdFinsh,
			extra: args
		};

		//为对象提供连接方法
		this._EventDispath(this.SocketTask);
		//初始化websocket
		this.initChat(this.SocketTask, this.SocketTask.extra);
		return this.SocketTask;

	}

	set CONCATCOUNT(value) {
		Socket.concatCount = value;
		if (value > 0) this._notify();
	}
	get CONCATCOUNT() {
		return Socket.concatCount
	}

	/**
	 * 每25s给服务端发送一次数据,确保连接未断开
	 */
	initSendTime() {
		Socket.sendInterval = setInterval(() => {
			if (Socket.isCommunication) {
				uni.showToast({
					icon: 'none',
					title: '网络异常',
					duration: 2000
				})
				this.SocketTask.nrconnect()
			} else {
				Socket.isCommunication = true
				this.SocketTask.nsend(2)
			}
		}, 25000)
		subscribe("number", function(msg, data) {
			Socket.isCommunication = false
		})
	}

	//销毁定时器
	destroySend() {
		clearTimeout(Socket.errorNr)
		clearTimeout(Socket.stopTime);
		clearInterval(Socket.sendInterval)
		unsubscribe("number")
	}
	/**
	 * 仅供内部使用,通知所有收集到的依赖
	 */
	_notify() {
		for (let i = 0; i < this.waitDep.length; i++) {
			this.waitDep[i].call(this.SocketTask);
		}
		this.waitDep = [];
	}
	/**
	 * 仅供内部使用,确认当前是否连接成功,收集依赖
	 */
	_chunkConnect(fn) {
		//没有连接时收集起来,当连接成功时开始发送命令等操作
		if (Socket.concatCount > 0) {
			fn();
		} else {
			this.waitDep.push(fn);
		}
	}
	/**
	 * 仅供内部使用,事件注册
	 */
	_EventDispath({
		onReload
	} = {}) {
		let SocketTask = this.SocketTask;
		let events = {
			onOpen: [],
			onMsg: [],
			onClose: [],
			onError: [],
			onReload: [],
			onRdFinsh: [],
		}
		SocketTask.hbDetection = () => {
			this.hbDetection()
		}
		//适配后端websocket发送请求数据
		//vue-socket-io 传入的是数组,第一位为key值
		SocketTask.nsend = (key, value) => {
			let that = this
			this._chunkConnect(() => {
				let data = "42"
				//web端连接或断开连接的时候传入420 421
				if (Socket.isCallback.includes(key)) {
					//收集当前连接的命令key值,等待返回响应时传递给客户端处理逻辑
					Socket.sendKey = key
					//421 422 ··· 依次递增
					data += Socket.sendTag
					Socket.sendTag++
				}
				if (value) {
					data += JSON.stringify([key, value])
				} else {
					//特殊情况,当想要发送的请求不为42【key,value】格式时,key为客户端代码发送传入的值
					data = key + ""
				}

				uni.sendSocketMessage({
					//pc端emit事件有发送完毕回调函数时后端会需要有多一位数字的判断,并且返回时也会产生eg:420 -》 430
					// 没有回调函数时发送和接收命令前面都是42
					data: data,
					complete(e) {},
				})
			})
		}
		SocketTask.nclose = t => {
			this.destroySend()
			this._chunkConnect(() => {
				SocketTask.uniColse = true;
				uni.closeSocket();
			})
		}
		SocketTask.nrconnect = t => {
			this._chunkConnect(() => {
				this.waiting = new Promise(async (resolve) => {
					uni.closeSocket();
					let reloadStatus = false;
					try {
						const res = await this.initChat(SocketTask, SocketTask.extra);
						reloadStatus = res;
					} catch (e) {}
					onReload.call(SocketTask, reloadStatus, SocketTask);
					SocketTask.eventPatch.dispatchEvent('onReload', reloadStatus);
					resolve(reloadStatus);
				})
			})
		}

		function EventDispatcher() {
			this.events = events;
		}
		for (let key in events) {
			//绑定监听方法到EventDispatcher方法原型中
			EventDispatcher.prototype[key] = function(handler) {
				if (typeof handler != 'function') return;
				this.events[key].push(handler)
			}
		}
		//调用监听方法 监听连接状态
		EventDispatcher.prototype.dispatchEvent = function(type, msg) {
			let evenArr = this.events[type];
			if (evenArr.length > 0) {
				for (let i = 0; i < evenArr.length; i++) {
					evenArr[i].call(SocketTask, msg, SocketTask);
				}
			}
		}
		SocketTask.eventPatch = new EventDispatcher();
	}
	/**
	 * 心跳检测
	 */
	async hbDetection() {
		const SocketTask = this.SocketTask;
		if (SocketTask.uniColse) {
			return false;
		}
		clearTimeout(Socket.stopTime);
		if (!SocketTask.isconnect) { //未连接则启动连接
			if (SocketTask.maxInterValCount > SocketTask.InterValCount) {
				Socket.stopTime = setTimeout(async () => {
					try {
						const R_result = await this.waiting;
						if (R_result) return;
						this.isRconnectIng = true;
						const openResult = await this.initChat(SocketTask, SocketTask.extra);
						if (openResult) {
							SocketTask.InterValCount++;
							return;
						}
						return this.hbDetection();
					} catch (e) {
						return this.hbDetection();
					}
				}, SocketTask.interValTime)
			} else {
				SocketTask.onRdFinsh.call(SocketTask, SocketTask.maxInterValCount, SocketTask);
				SocketTask.eventPatch.dispatchEvent('onRdFinsh', SocketTask.maxInterValCount);
			}
		}
	}
	/**
	 * websocket监听事件
	 */
	SocketEvents({
		onOpen,
		onMsg,
		onClose,
		onError,
		onReload,
	} = {}) {
		return new Promise((resolve, reject) => {
			const SocketTask = this.SocketTask;
			uni.onSocketOpen(res => {
				this.CONCATCOUNT += 1;
				this.isRconnectIng = false;
				SocketTask.isconnect = true;
				Socket.sendTag = 0
				Socket.sendKey = ""
				SocketTask.InterValCount = 0;
				SocketTask.uniColse = false;
				resolve(true);
				this.initSendTime()
				onOpen.call(SocketTask, res, SocketTask);
				SocketTask.eventPatch.dispatchEvent('onOpen', res)
			})

			//适配服务器端返回的消息格式
			uni.onSocketMessage(msg => {
				let serialNumber = 0
				let arrayIndex = msg.data.indexOf("[")
				let objIndex = msg.data.indexOf("{")

				//判断返回值是纯数字或json格式
				let dataStart = arrayIndex != -1 && objIndex != -1 ?
					(arrayIndex < objIndex ? arrayIndex : objIndex) :
					(arrayIndex == -1 ? objIndex : arrayIndex)


				if (dataStart == -1) {
					serialNumber = msg.data.substring(0)
					publish("number", serialNumber)
				} else {
					serialNumber = msg.data.substring(0, dataStart)
					let jsonData = JSON.parse(msg.data.substring(dataStart))

					//判断是否为连接事件
					if (serialNumber == `43${Socket.sendTag - 1}`) {
						publish(Socket.sendKey, jsonData[0])
					} else if (isArray(jsonData)) {
						publish(jsonData[0], jsonData[1])
					}

				}

				onMsg.call(SocketTask, msg, SocketTask);
				SocketTask.eventPatch.dispatchEvent('onMsg', msg)
			})
			uni.onSocketClose(async err => {
				this.destroySend()
				SocketTask.isconnect = false;
				SocketTask.InterValCount = 0;
				//微信小程序 连接失败会自动关闭连接,返回false给心跳监测方法
				resolve(false);
				if (!this.isRconnectIng) {
					this.hbDetection();
				}
				onClose.call(SocketTask, err, SocketTask);
				SocketTask.eventPatch.dispatchEvent('onClose', err);
			})
			uni.onSocketError(err => {
				this.destroySend()
				Socket.errorNr = setTimeout(() => {
					this.hbDetection()
				}, 3000)
				onError.call(SocketTask, err, SocketTask);
				SocketTask.eventPatch.dispatchEvent('onError', err)
			})
		})
	}
	/**
	 * 开始初始化chat
	 */
	initChat({
		url,
		onOpen,
		onMsg,
		onClose,
		onError,
		onReload
	} = {}, args) {
		return new Promise(async (resolve, reject) => {
			try {
				await this.connectSocket(url, args);
				let res = await this.SocketEvents({
					onOpen,
					onMsg,
					onClose,
					onError,
					onReload,
				})
				resolve(res);
			} catch (e) {
				console.log('initChat', e)
				reject();
			}
		})
	}
	/**
	 * 连接webSocket
	 */
	connectSocket(url, args) {
		return new Promise((resolve, reject) => {
			uni.connectSocket({
				url,
				success: () => {
					resolve();
				},
				fail: err => {
					reject();
				},
				...args
			})
		})
	}
}
export default Socket

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

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

相关文章

IDEA-控制台日志过滤插件 - Grep Console

IDEA-控制台日志过滤插件 - Grep Console 当idea控制台日志较多时&#xff0c;为了方便查找关键字&#xff0c;使用Grep Console插件&#xff0c;指定控制台中关键字高亮显示 1.安装 2.使用 2.1 高亮显示 控制台中指定颜色高亮显示指定字符 效果: 重启项目后还是会高亮显示 取…

【软考高项】三十三、质量管理

一、管理基础 质量定义 国际标准&#xff1a;反映实体满足主体明确和隐含需求的能力的特性总和。 国家标准&#xff1a;一组固有特性满足要求的程度。固有特性是指在某事或某物中本来就有的&#xff0c;尤其是那种永久的可区分的特征。 ➢ 对产品来说&#xff0c;例如…

缓存菜品操作

一&#xff1a;问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大。 二&#xff1a;实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; 每个分…

k8s保持pod健康

存活探针 Kubemetes 可以通过存活探针 (liveness probe) 检查容器是否还在运行。可以为 pod 中的每个容器单独指定存活探针。如果探测失败&#xff0c;Kubemetes 将定期执行探针并重新启动容器。 Kubemetes 有以下三种探测容器的机制&#xff1a; HTTP GET 探针对容器的 IP 地…

Day61:单调栈 739. 每日温度 496.下一个更大元素 I

739. 每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 示例 1: 输…

发表博客之:gemm/threadblock/threadblock_swizzle.h 文件夹讲解,cutlass深入讲解

文章目录 [发表博客之&#xff1a;gemm/threadblock/threadblock_swizzle.h 文件夹讲解&#xff0c;cutlass深入讲解](https://cyj666.blog.csdn.net/article/details/138514145)先来看一下最简单的struct GemmIdentityThreadblockSwizzle结构体 发表博客之&#xff1a;gemm/th…

vue2 webpack-dev-server Unknown promise rejection reason

在vue.config.js中添加如下配置&#xff0c;重启项目即可 module.exports defineConfig({devServer: {client: {overlay: false,},} })参考

探索中位数快速排序算法:高效寻找数据集的中间值

在计算机科学领域&#xff0c;寻找数据集的中位数是一个常见而重要的问题。而快速排序算法作为一种高效的排序算法&#xff0c;可以被巧妙地利用来解决中位数查找的问题。本文将深入探讨中位数快速排序算法的原理、实现方法以及应用场景&#xff0c;带你领略这一寻找中间值的高…

vue 金额组件,输入提示单位:‘千’、‘万’、‘十万’...并用‘,’三个格式化

近期项目中遇到一个需求&#xff0c;金额输入框&#xff0c;输入过程中自动提示‘千’、‘万’、‘十万’、‘百万’......等单位提示&#xff0c;鼠标失去焦点后&#xff0c;并用‘,’三位隔开计数。 例如&#xff1a; 输入&#xff1a;12345.99 失去焦点&#xff1a;12,34…

Vue--》从零开始打造交互体验一流的电商平台(一)

今天开始使用 vue3 ts 搭建一个电商项目平台&#xff0c;因为文章会将项目的每处代码的书写都会讲解到&#xff0c;所以本项目会分成好几篇文章进行讲解&#xff0c;我会在最后一篇文章中会将项目代码开源到我的github上&#xff0c;大家可以自行去进行下载运行&#xff0c;希…

【Node.js工程师养成计划】之express中间件与接口规范

一、Express中间件的概念与基本应用 const express require(express)// 加一个注释&#xff0c;用以说明&#xff0c;本项目代码可以任意定制更改 const app express()const PORT process.env.PORT || 3000// // 挂载路由 // app.use(/api, router)// // 挂载统一处理服务端…

【倪亲斫经典水墨云纹仲尼式】倪诗韵亲斫古琴

【倪亲斫经典水墨云纹仲尼式】倪诗韵亲斫古琴 松透润&#xff0c;适合大曲文曲潇湘欸乃平沙&#xff0c;余韵悠长&#xff0c;手感极其舒适&#xff0c;久弹不疲。

[Linux][网络][TCP][三][超时重传][快速重传][SACK][D-SACK][滑动窗口]详细讲解

目录 1.超时重传1.什么是超时重传&#xff1f;2.超时时间是如何确定的&#xff1f; 2.快速重传3.SACK4.D-SACK1.ACK丢失2.网络延迟 5.滑动窗口0.问题抛出1.发送方的滑动窗口2.如何表示发送方的四个部分&#xff1f;3.接收方的滑动窗口4.滑动窗口的完善理解 1.超时重传 1.什么是…

C++手写协程项目(协程实现线程结构体、线程调度器定义,线程挂起函数、线程切换函数、线程恢复函数、线程结束函数、线程结束判断函数,模块测试)

协程结构体定义 之前我们使用linux下协程函数实现了线程切换&#xff0c;使用的是ucontext_t结构体&#xff0c;和基于这个结构体的四个函数。现在我们要用这些工具来实现我们自己的一个线程结构体&#xff0c;并实现线程调度和线程切换、挂起。 首先我们来实现以下线程结构体…

Splay 树简介

【Splay 树简介】 ● Treap 树解决平衡的办法是给每个结点加上一个随机的优先级&#xff0c;实现概率上的平衡。Splay 树直接用旋转调整树的形态&#xff0c;通过旋转改善树的平衡性。计算量小&#xff0c;效果好。 ● Splay 树的旋转主要分为“单旋”和“双旋”。 所谓“单旋”…

基于52单片机的AS608指纹密码锁电路原理图+源程序+PCB实物制作

目录 1、前言 2、实物图 3、PCB图 4、原理图 5、程序 资料下载地址&#xff1a;基于52单片机的AS608指纹密码锁电路原理图源程序PCB实物制作 1、前言 这是一个基于AS608STC89C52单片机的指纹识别和键盘密码锁。 里面包括程序&#xff0c;原理图&#xff0c;pcb图和实…

OpenNJet:云原生技术中的创新者与实践者

目录 引言OpenNJet介绍OpenNJet优势1. 性能无损动态配置2. 灵活的CoPilot框架3. 支持HTTP/34. 支持国密5. 企业级应用6. 高效安全 OpenNJet 编译与安装环境准备编译环境配置配置yum源yum 安装软件包创建符号连接修改 ld.so.conf 配置 编译代码 部署 WEB SERVER配置OpenNJet部署…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-13-按键实验

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

FTP协议与工作原理

一、FTP协议 FTP&#xff08;FileTransferProtocol&#xff09;文件传输协议&#xff1a;用于Internet上的控制文件的双向传输&#xff0c;是一个应用程序&#xff08;Application&#xff09;。基于不同的操作系统有不同的FTP应用程序&#xff0c;而所有这些应用程序都遵守同…

计算机网络【应用层】邮件和DNS

文章目录 电子邮件DNSDNS提供的服务&#xff1a;域名分级域名解析流程DNS资源记录DNS服务器类型 电子邮件 使用SMTP协议发送邮件之前&#xff0c;需要将二进制多媒体数据编码为ASCII码SMTP一般不使用中间邮件服务器发送邮件&#xff0c;如果收件服务器没开机&#xff0c;那么会…
最新文章