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

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

javascript 复制代码
//使用socket类
				this.socket = new websocket({
					url: chatUrl,
					onOpen: (res) => {
						this.socket?.nsend({
							type: "bind",
							fromid: uni.getStorageSync("project_user").uid
						})

						uni.$on("message", function(data) {
							that.recvMsg(data)
						})

						console.log('onOpen', res)
					},
					onClose: (res) => {
						console.log('onClose', res)
					},
					onRdFinsh: (res) => {
						console.log('onRdFinsh', res)
					}
				})
javascript 复制代码
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
相关推荐
音视频开发_AIZ1 分钟前
语聊房实时语音SDK选型:即构 vs 声网 vs 腾讯云深度对比
flutter·unity·uni-app·实时音视频·ai降噪·实时语音·语音社交
2501_915909064 分钟前
iPhone 手机日志实时查看,开发和测试中常用的几种方法
android·ios·智能手机·小程序·uni-app·iphone·webview
周淳APP16 分钟前
【HTTP之跨域请求以及Cookie携带的限制】
前端·网络·网络协议·http
周淳APP35 分钟前
【HTTP相关及RESTful】风萧萧兮易水寒之壮士学习不复返
前端·javascript·网络·网络协议·http·restful·jsonp
萝卜白菜。35 分钟前
http头Location是相对路径还是绝对路径
网络·网络协议·http
格鸰爱童话36 分钟前
springboot实现websocket在线聊天室
spring boot·后端·websocket
周淳APP40 分钟前
【计算机网络之HTTP、TCP、UDP、HTTPS、Socket网络连接】
前端·javascript·网络·网络协议·http·前端框架
博语小屋40 分钟前
HTTP详解
网络·网络协议·http
汤愈韬1 小时前
各类LSA的解析(一二三类LSA)
网络·网络协议·网络安全·security
Initialize-le5 小时前
WMware桥接模式配置静态IP上网
网络协议·tcp/ip·桥接模式