NetManager.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /** @format */
  2. import {Data, GameControl, Mgr} from '../GameControl'
  3. import {Log} from '../utils/LogUtils'
  4. import {EVENT, GAME_TYPE, LANGUAGE_TYPE} from '../enums/Enum'
  5. import {MSG} from '../enums/MSG'
  6. import {ProtoUtils} from '../proto/ProtoUtils'
  7. import {msgCmd} from '../proto/msg_cmd'
  8. import {ConstValue} from '../data/ConstValue'
  9. import {UI} from '../enums/UI'
  10. export class SendPackage {
  11. public type: number | string = 0
  12. public content: any = {}
  13. public showLoading: boolean = true
  14. public test?: boolean = false
  15. public errno?: number
  16. public handleErr?: boolean
  17. }
  18. export class NetManager {
  19. // webSocket连接
  20. public webSocket: WebSocket = null
  21. // 消息业务类型
  22. private msgType: Map<string, any> = new Map<string, any>()
  23. // 消息业务类型
  24. private msgList: Map<number, any> = new Map<number, any>()
  25. // 重连设定
  26. private autoReconnect: number = 3 // 0 不自动重连,其他正整数为自动重试次数
  27. // 记录当前等待返回的所有showLoading的key
  28. private showLoadingMap: Map<string, SendPackage> = new Map<string, SendPackage>()
  29. private _url: string = 'ws://192.168.31.100:8001'
  30. public get url(): string {
  31. return this._url
  32. }
  33. public set url(value: string) {
  34. this._url = value
  35. }
  36. private reTryFailCallBack: Function = null
  37. private timeOutCallBack: Function = null
  38. private maintainCallBack: Function = null
  39. private kickOffCallBack: Function = null
  40. private timeOutID: any = null // 接收数据定时器
  41. private timeOutTime: number = 10000 // 超时时间 达到会主动关闭连接
  42. private keepAliveID: any = null // 心跳定时器
  43. private keepAliveTime: number = 5000 // 心跳间隔 心跳间隔必须小于超时时间
  44. private serverTimeID: any = null // 服务器时间定时器
  45. private reConnectTime: number = 2000 // 重连间隔
  46. private curAutoReconnect: number = 0 // 当前重连次数
  47. private isMaxReconnect: boolean = true
  48. public init() {
  49. let reLogin = () => {
  50. Mgr.ui.hideLoading()
  51. Mgr.ui.message(
  52. LANGUAGE_TYPE.netCut,
  53. () => {
  54. GameControl.resetGame()
  55. },
  56. true,
  57. )
  58. }
  59. this.setReTryFailCallback(reLogin)
  60. this.setTimeOutCallback(reLogin)
  61. }
  62. public reset() {
  63. this.clearLoading()
  64. this.close()
  65. Data.main.webSocketCon = false
  66. }
  67. // 设置重试失败回调
  68. public setReTryFailCallback(reTryFailCallBack: Function) {
  69. this.reTryFailCallBack = reTryFailCallBack
  70. }
  71. // 设置超时回调
  72. public setTimeOutCallback(timeOutCallBack: Function) {
  73. this.timeOutCallBack = timeOutCallBack
  74. }
  75. // 设置维护回调
  76. public setMaintainCallback(maintainCallBack: Function) {
  77. this.maintainCallBack = maintainCallBack
  78. }
  79. // 设置被踢回调
  80. public setKickOffCallback(kickOffCallBack: Function) {
  81. this.kickOffCallBack = kickOffCallBack
  82. }
  83. public reg(keyName: string, id: number, protoType) {
  84. this.msgType.set(this.getKey(id), {id, keyName, protoType})
  85. }
  86. public getKey(id: number) {
  87. return msgCmd[id]
  88. }
  89. public add(id: number, caller: any, listener: Function, ...argArray: any[]) {
  90. Mgr.event.add(this.getKey(id), caller, listener, argArray)
  91. }
  92. public addTop(id: number, caller: any, listener: Function, ...argArray: any[]) {
  93. Mgr.event.addTop(this.getKey(id), caller, listener, argArray)
  94. }
  95. public addOnce(id: number, caller: any, listener: Function, ...argArray: any[]) {
  96. Mgr.event.addOnce(this.getKey(id), caller, listener, argArray)
  97. }
  98. public del(id: number, caller: any, listener: Function) {
  99. Mgr.event.remove(this.getKey(id), caller, listener)
  100. }
  101. public connect() {
  102. this.close()
  103. try {
  104. if (Mgr.platform.isAndroid()) {
  105. // @ts-ignore
  106. this.webSocket = new WebSocket(this.url, null, cc.url.raw('resources/ssl/cacert.pem'))
  107. } else {
  108. this.webSocket = new WebSocket(this.url)
  109. }
  110. console.log('connect WebSocket:', this.url)
  111. this.webSocket.binaryType = 'arraybuffer'
  112. this.webSocket.onopen = this.onOpen.bind(this)
  113. this.webSocket.onmessage = this.onMessage.bind(this)
  114. this.webSocket.onerror = this.onError.bind(this)
  115. this.webSocket.onclose = this.onClose.bind(this)
  116. } catch (e) {
  117. console.error('connect WebSocket err:', e)
  118. }
  119. }
  120. public close() {
  121. Log.net('close')
  122. this.stopKeepAlive()
  123. if (this.webSocket && this.webSocket.readyState == WebSocket.OPEN) {
  124. this.webSocket.onopen = null
  125. this.webSocket.onmessage = null
  126. this.webSocket.onerror = null
  127. this.webSocket.onclose = null
  128. this.webSocket.close()
  129. Mgr.event.trigger(EVENT.webSocketClose)
  130. } else {
  131. Log.warn('close err: this.webSocket is null or is not OPEN')
  132. }
  133. }
  134. public sendKeepAlive() {
  135. if (this.keepAliveID !== null) {
  136. clearTimeout(this.keepAliveID)
  137. }
  138. this.keepAliveID = setTimeout(this.keepAlive.bind(this), this.keepAliveTime)
  139. }
  140. public stopKeepAlive() {
  141. if (this.timeOutID !== null) {
  142. clearTimeout(this.timeOutID)
  143. }
  144. if (this.keepAliveID !== null) {
  145. clearTimeout(this.keepAliveID)
  146. }
  147. }
  148. private keepAlive() {
  149. this.send(msgCmd.cmd_ping, null, false)
  150. if (this.timeOutID !== null) {
  151. clearTimeout(this.timeOutID)
  152. }
  153. this.timeOutID = setTimeout(this.aliveTimeOut.bind(this), this.timeOutTime)
  154. }
  155. private aliveTimeOut() {
  156. Log.net('心跳消息超时,链接断开')
  157. this.close()
  158. this.timeOutCallBack && this.timeOutCallBack()
  159. }
  160. private sendData(data: SendPackage, isTest: boolean = false) {
  161. if (data) {
  162. let key = typeof data.type == 'number' ? this.getKey(data.type) : data.type
  163. //临时将协议number改成协议string
  164. data.type = key.replace('cmd_', '')
  165. if (key != msgCmd[msgCmd.cmd_ping] && data.showLoading) {
  166. //Mgr.ui.showLoading(LANGUAGE_TYPE.loading, true, false)
  167. this.showLoadingMap.set(key, data)
  168. }
  169. if (isTest) {
  170. Log.net('testSend ->', key, '\n', data.content)
  171. let testResp = {data: JSON.stringify(data)}
  172. this.onMessage(testResp)
  173. } else {
  174. //this.webSocket.send(JSON.stringify(data))
  175. let bytes = ProtoUtils.Encode(data.type, data.content)
  176. let buffer = new ArrayBuffer(4 + bytes.byteLength)
  177. new DataView(buffer).setInt32(0, msgCmd[key])
  178. new Uint8Array(buffer, 4).set(new Uint8Array(bytes))
  179. this.webSocket.send(new Uint8Array(buffer))
  180. if (key != msgCmd[msgCmd.cmd_ping]) {
  181. Log.net('send ->', key, '\n', data.content)
  182. }
  183. }
  184. }
  185. }
  186. /**
  187. *
  188. * @param key 协议key
  189. * @param data 要发送的数据
  190. * @param showLoading 是否显示响应保护
  191. */
  192. public send(id: number, data: any = null, showLoading: boolean = true, handleErr: boolean = false) {
  193. let test = false
  194. if (data && data.test) {
  195. test = data.test
  196. delete data.test
  197. }
  198. if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
  199. this.sendData({type: this.getKey(id), content: data, showLoading}, test)
  200. }
  201. }
  202. public getSendPackage(id: number, data: any = null, showLoading: boolean = true): SendPackage {
  203. let newPackage = new SendPackage()
  204. newPackage.type = this.getKey(id)
  205. newPackage.content = data
  206. newPackage.showLoading = showLoading
  207. return newPackage
  208. }
  209. public sendEx(sendList: SendPackage[], overCallback: Function) {
  210. let batchKey = new Date().getTime()
  211. let batchUnit: any = {}
  212. batchUnit.sendList = sendList
  213. batchUnit.retList = new Map<string, any>()
  214. for (let i = 0; i < sendList.length; i++) {
  215. batchUnit.retList.set(sendList[i].type, null)
  216. }
  217. batchUnit.overCallback = overCallback
  218. this.msgList.set(batchKey, batchUnit)
  219. this.tryStepSendEx()
  220. }
  221. public tryStepSendEx(key = null, ret = null) {
  222. let msgListKeys = this.msgList.keysArr()
  223. if (msgListKeys.length > 0) {
  224. let batchUnit = this.msgList.get(msgListKeys[0])
  225. if (!batchUnit) return
  226. if (key && batchUnit.retList.get(key) !== undefined) {
  227. batchUnit.retList.set(key, ret)
  228. if (ret > 0) {
  229. //先执行delete,如果overCallback中有sendEx会插入一段overCallback,导致overCallback执行两次
  230. this.msgList.delete(msgListKeys[0])
  231. if (batchUnit.overCallback) {
  232. batchUnit.overCallback(false)
  233. }
  234. return
  235. }
  236. }
  237. let data = batchUnit.sendList.shift()
  238. if (data) {
  239. let test = false
  240. if (data.test) {
  241. test = data.test
  242. delete data.test
  243. }
  244. this.sendData(data, test)
  245. } else {
  246. //先执行delete,如果overCallback中有sendEx会插入一段overCallback,导致overCallback执行两次
  247. this.msgList.delete(msgListKeys[0])
  248. if (batchUnit.overCallback) {
  249. batchUnit.overCallback(true)
  250. }
  251. }
  252. }
  253. }
  254. public encode(key: any, data: any) {}
  255. public decode(data): any {}
  256. private onOpen(event) {
  257. Log.net('onOpen')
  258. Mgr.event.trigger(EVENT.webSocketOpen)
  259. this.sendKeepAlive()
  260. this.curAutoReconnect = 0
  261. }
  262. private onClose(event) {
  263. Log.net('onClose event:', event)
  264. setTimeout(this.reConnect.bind(this), this.reConnectTime)
  265. Mgr.event.trigger(EVENT.webSocketClose)
  266. }
  267. public reConnect() {
  268. // 开始自动重连
  269. if (this.autoReconnect <= 0) return
  270. if (this.isMaxReconnect) this.curAutoReconnect += 1
  271. if (this.curAutoReconnect < this.autoReconnect) {
  272. this.connect()
  273. } else {
  274. if (this.reTryFailCallBack) {
  275. this.reTryFailCallBack()
  276. }
  277. }
  278. }
  279. public stopReConnect() {
  280. this.autoReconnect = 0
  281. }
  282. public setReConnect() {
  283. this.autoReconnect = 4
  284. }
  285. private onMessage(evt: any) {
  286. let {data} = evt
  287. //let obj = JSON.parse(data)
  288. let type = new DataView(data).getInt32(0)
  289. let content = ProtoUtils.Decode(msgCmd[type], new Uint8Array(data.slice(4)))
  290. this.msgTrigger({showLoading: false, type, content, errno: content.errno})
  291. // try {
  292. // this.msgTrigger(obj)
  293. // } catch (e) {
  294. // console.error('websocket onMessage', e)
  295. // }
  296. }
  297. public msgTrigger(object: SendPackage) {
  298. if (object) {
  299. let key = typeof object.type == 'number' ? this.getKey(object.type) : 'cmd_' + object.type
  300. if (key != msgCmd[msgCmd.cmd_ping_rsp]) {
  301. Log.net('onMessage Resp ->', key, object)
  302. } else {
  303. this.sendKeepAlive()
  304. }
  305. let sendKey = key.replace('_rsp', '')
  306. this.tryStepSendEx(sendKey, object.errno)
  307. this.showLoadingMap.delete(sendKey)
  308. if (this.showLoadingMap.size == 0) {
  309. //Mgr.ui.hideLoading()
  310. }
  311. //处理心跳
  312. if (key == msgCmd[msgCmd.cmd_ping_rsp]) {
  313. if (object.content.time != Data.main.serverTime && Data.main.loginTime) {
  314. Data.main.serverTime = object.content.time
  315. Data.main.secondTime = Data.main.serverTime - Data.main.loginTime
  316. Data.main.globalTime = Data.main.secondTime
  317. Data.main.minutesTime = Math.floor(Data.main.secondTime / 60)
  318. Mgr.time.globalTimer()
  319. }
  320. }
  321. //处理异常和登录失败
  322. else if (key == msgCmd[msgCmd.cmd_exception_nty] || (key == msgCmd[msgCmd.cmd_login] && object.errno)) {
  323. this.stopReConnect()
  324. Mgr.ui.message(
  325. Mgr.i18n.getLabel('e' + object.errno.toString()),
  326. () => {
  327. GameControl.resetGame()
  328. },
  329. true,
  330. )
  331. }
  332. //处理普通消息
  333. else {
  334. if (!object.errno || object.handleErr) {
  335. let isTrigger = Mgr.event.trigger(key, object.content, Data.main.rewardNtyMap.get(key))
  336. if (!isTrigger) Log.warn('Not Found Resp callback reg!', key)
  337. } else {
  338. Mgr.ui.tip(Mgr.i18n.getLabel('e' + object.errno.toString()))
  339. }
  340. }
  341. Data.main.rewardNtyMap.delete(key)
  342. }
  343. }
  344. /**
  345. * 处理服务器踢人通知
  346. */
  347. private kickOffNty(data: any) {
  348. console.info('server kick off!')
  349. if (this.kickOffCallBack) {
  350. this.kickOffCallBack(data)
  351. }
  352. }
  353. private onError(event) {
  354. console.error('websocket onError', event)
  355. }
  356. private clearLoading() {
  357. this.showLoadingMap.clear()
  358. }
  359. }