/** @format */ import {Data, GameControl, Mgr} from '../GameControl' import {Log} from '../utils/LogUtils' import {EVENT, GAME_TYPE, LANGUAGE_TYPE} from '../enums/Enum' import {MSG} from '../enums/MSG' import {ProtoUtils} from '../proto/ProtoUtils' import {msgCmd} from '../proto/msg_cmd' import {ConstValue} from '../data/ConstValue' import {UI} from '../enums/UI' export class SendPackage { public type: number | string = 0 public content: any = {} public showLoading: boolean = true public test?: boolean = false public errno?: number public handleErr?: boolean } export class NetManager { // webSocket连接 public webSocket: WebSocket = null // 消息业务类型 private msgType: Map = new Map() // 消息业务类型 private msgList: Map = new Map() // 重连设定 private autoReconnect: number = 3 // 0 不自动重连,其他正整数为自动重试次数 // 记录当前等待返回的所有showLoading的key private showLoadingMap: Map = new Map() private _url: string = 'ws://192.168.31.100:8001' public get url(): string { return this._url } public set url(value: string) { this._url = value } private reTryFailCallBack: Function = null private timeOutCallBack: Function = null private maintainCallBack: Function = null private kickOffCallBack: Function = null private timeOutID: any = null // 接收数据定时器 private timeOutTime: number = 10000 // 超时时间 达到会主动关闭连接 private keepAliveID: any = null // 心跳定时器 private keepAliveTime: number = 5000 // 心跳间隔 心跳间隔必须小于超时时间 private serverTimeID: any = null // 服务器时间定时器 private reConnectTime: number = 2000 // 重连间隔 private curAutoReconnect: number = 0 // 当前重连次数 private isMaxReconnect: boolean = true public init() { let reLogin = () => { Mgr.ui.hideLoading() Mgr.ui.message( LANGUAGE_TYPE.netCut, () => { GameControl.resetGame() }, true, ) } this.setReTryFailCallback(reLogin) this.setTimeOutCallback(reLogin) } public reset() { this.clearLoading() this.close() Data.main.webSocketCon = false } // 设置重试失败回调 public setReTryFailCallback(reTryFailCallBack: Function) { this.reTryFailCallBack = reTryFailCallBack } // 设置超时回调 public setTimeOutCallback(timeOutCallBack: Function) { this.timeOutCallBack = timeOutCallBack } // 设置维护回调 public setMaintainCallback(maintainCallBack: Function) { this.maintainCallBack = maintainCallBack } // 设置被踢回调 public setKickOffCallback(kickOffCallBack: Function) { this.kickOffCallBack = kickOffCallBack } public reg(keyName: string, id: number, protoType) { this.msgType.set(this.getKey(id), {id, keyName, protoType}) } public getKey(id: number) { return msgCmd[id] } public add(id: number, caller: any, listener: Function, ...argArray: any[]) { Mgr.event.add(this.getKey(id), caller, listener, argArray) } public addTop(id: number, caller: any, listener: Function, ...argArray: any[]) { Mgr.event.addTop(this.getKey(id), caller, listener, argArray) } public addOnce(id: number, caller: any, listener: Function, ...argArray: any[]) { Mgr.event.addOnce(this.getKey(id), caller, listener, argArray) } public del(id: number, caller: any, listener: Function) { Mgr.event.remove(this.getKey(id), caller, listener) } public connect() { this.close() try { if (Mgr.platform.isAndroid()) { // @ts-ignore this.webSocket = new WebSocket(this.url, null, cc.url.raw('resources/ssl/cacert.pem')) } else { this.webSocket = new WebSocket(this.url) } console.log('connect WebSocket:', this.url) this.webSocket.binaryType = 'arraybuffer' this.webSocket.onopen = this.onOpen.bind(this) this.webSocket.onmessage = this.onMessage.bind(this) this.webSocket.onerror = this.onError.bind(this) this.webSocket.onclose = this.onClose.bind(this) } catch (e) { console.error('connect WebSocket err:', e) } } public close() { Log.net('close') this.stopKeepAlive() if (this.webSocket && this.webSocket.readyState == WebSocket.OPEN) { this.webSocket.onopen = null this.webSocket.onmessage = null this.webSocket.onerror = null this.webSocket.onclose = null this.webSocket.close() Mgr.event.trigger(EVENT.webSocketClose) } else { Log.warn('close err: this.webSocket is null or is not OPEN') } } public sendKeepAlive() { if (this.keepAliveID !== null) { clearTimeout(this.keepAliveID) } this.keepAliveID = setTimeout(this.keepAlive.bind(this), this.keepAliveTime) } public stopKeepAlive() { if (this.timeOutID !== null) { clearTimeout(this.timeOutID) } if (this.keepAliveID !== null) { clearTimeout(this.keepAliveID) } } private keepAlive() { this.send(msgCmd.cmd_ping, null, false) if (this.timeOutID !== null) { clearTimeout(this.timeOutID) } this.timeOutID = setTimeout(this.aliveTimeOut.bind(this), this.timeOutTime) } private aliveTimeOut() { Log.net('心跳消息超时,链接断开') this.close() this.timeOutCallBack && this.timeOutCallBack() } private sendData(data: SendPackage, isTest: boolean = false) { if (data) { let key = typeof data.type == 'number' ? this.getKey(data.type) : data.type //临时将协议number改成协议string data.type = key.replace('cmd_', '') if (key != msgCmd[msgCmd.cmd_ping] && data.showLoading) { //Mgr.ui.showLoading(LANGUAGE_TYPE.loading, true, false) this.showLoadingMap.set(key, data) } if (isTest) { Log.net('testSend ->', key, '\n', data.content) let testResp = {data: JSON.stringify(data)} this.onMessage(testResp) } else { //this.webSocket.send(JSON.stringify(data)) let bytes = ProtoUtils.Encode(data.type, data.content) let buffer = new ArrayBuffer(4 + bytes.byteLength) new DataView(buffer).setInt32(0, msgCmd[key]) new Uint8Array(buffer, 4).set(new Uint8Array(bytes)) this.webSocket.send(new Uint8Array(buffer)) if (key != msgCmd[msgCmd.cmd_ping]) { Log.net('send ->', key, '\n', data.content) } } } } /** * * @param key 协议key * @param data 要发送的数据 * @param showLoading 是否显示响应保护 */ public send(id: number, data: any = null, showLoading: boolean = true, handleErr: boolean = false) { let test = false if (data && data.test) { test = data.test delete data.test } if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { this.sendData({type: this.getKey(id), content: data, showLoading}, test) } } public getSendPackage(id: number, data: any = null, showLoading: boolean = true): SendPackage { let newPackage = new SendPackage() newPackage.type = this.getKey(id) newPackage.content = data newPackage.showLoading = showLoading return newPackage } public sendEx(sendList: SendPackage[], overCallback: Function) { let batchKey = new Date().getTime() let batchUnit: any = {} batchUnit.sendList = sendList batchUnit.retList = new Map() for (let i = 0; i < sendList.length; i++) { batchUnit.retList.set(sendList[i].type, null) } batchUnit.overCallback = overCallback this.msgList.set(batchKey, batchUnit) this.tryStepSendEx() } public tryStepSendEx(key = null, ret = null) { let msgListKeys = this.msgList.keysArr() if (msgListKeys.length > 0) { let batchUnit = this.msgList.get(msgListKeys[0]) if (!batchUnit) return if (key && batchUnit.retList.get(key) !== undefined) { batchUnit.retList.set(key, ret) if (ret > 0) { //先执行delete,如果overCallback中有sendEx会插入一段overCallback,导致overCallback执行两次 this.msgList.delete(msgListKeys[0]) if (batchUnit.overCallback) { batchUnit.overCallback(false) } return } } let data = batchUnit.sendList.shift() if (data) { let test = false if (data.test) { test = data.test delete data.test } this.sendData(data, test) } else { //先执行delete,如果overCallback中有sendEx会插入一段overCallback,导致overCallback执行两次 this.msgList.delete(msgListKeys[0]) if (batchUnit.overCallback) { batchUnit.overCallback(true) } } } } public encode(key: any, data: any) {} public decode(data): any {} private onOpen(event) { Log.net('onOpen') Mgr.event.trigger(EVENT.webSocketOpen) this.sendKeepAlive() this.curAutoReconnect = 0 } private onClose(event) { Log.net('onClose event:', event) setTimeout(this.reConnect.bind(this), this.reConnectTime) Mgr.event.trigger(EVENT.webSocketClose) } public reConnect() { // 开始自动重连 if (this.autoReconnect <= 0) return if (this.isMaxReconnect) this.curAutoReconnect += 1 if (this.curAutoReconnect < this.autoReconnect) { this.connect() } else { if (this.reTryFailCallBack) { this.reTryFailCallBack() } } } public stopReConnect() { this.autoReconnect = 0 } public setReConnect() { this.autoReconnect = 4 } private onMessage(evt: any) { let {data} = evt //let obj = JSON.parse(data) let type = new DataView(data).getInt32(0) let content = ProtoUtils.Decode(msgCmd[type], new Uint8Array(data.slice(4))) this.msgTrigger({showLoading: false, type, content, errno: content.errno}) // try { // this.msgTrigger(obj) // } catch (e) { // console.error('websocket onMessage', e) // } } public msgTrigger(object: SendPackage) { if (object) { let key = typeof object.type == 'number' ? this.getKey(object.type) : 'cmd_' + object.type if (key != msgCmd[msgCmd.cmd_ping_rsp]) { Log.net('onMessage Resp ->', key, object) } else { this.sendKeepAlive() } let sendKey = key.replace('_rsp', '') this.tryStepSendEx(sendKey, object.errno) this.showLoadingMap.delete(sendKey) if (this.showLoadingMap.size == 0) { //Mgr.ui.hideLoading() } //处理心跳 if (key == msgCmd[msgCmd.cmd_ping_rsp]) { if (object.content.time != Data.main.serverTime && Data.main.loginTime) { Data.main.serverTime = object.content.time Data.main.secondTime = Data.main.serverTime - Data.main.loginTime Data.main.globalTime = Data.main.secondTime Data.main.minutesTime = Math.floor(Data.main.secondTime / 60) Mgr.time.globalTimer() } } //处理异常和登录失败 else if (key == msgCmd[msgCmd.cmd_exception_nty] || (key == msgCmd[msgCmd.cmd_login] && object.errno)) { this.stopReConnect() Mgr.ui.message( Mgr.i18n.getLabel('e' + object.errno.toString()), () => { GameControl.resetGame() }, true, ) } //处理普通消息 else { if (!object.errno || object.handleErr) { let isTrigger = Mgr.event.trigger(key, object.content, Data.main.rewardNtyMap.get(key)) if (!isTrigger) Log.warn('Not Found Resp callback reg!', key) } else { Mgr.ui.tip(Mgr.i18n.getLabel('e' + object.errno.toString())) } } Data.main.rewardNtyMap.delete(key) } } /** * 处理服务器踢人通知 */ private kickOffNty(data: any) { console.info('server kick off!') if (this.kickOffCallBack) { this.kickOffCallBack(data) } } private onError(event) { console.error('websocket onError', event) } private clearLoading() { this.showLoadingMap.clear() } }