SocketClient.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. const sproto = require('sproto');
  2. const SocketUtil = require('./SocketUtil');
  3. cc.Class({
  4. name: 'SocketClient',
  5. properties: {
  6. _clientHost: undefined,
  7. _clientSender: undefined,
  8. _ws: undefined,
  9. _session: 0,
  10. _reconnectTimes: 0,
  11. reconnectMaxTimes: 3,
  12. _connectTimeoutId: undefined,
  13. connectTimeout: 10000,
  14. host: undefined,
  15. _requestCmds: undefined,
  16. _requestModels: undefined,
  17. _responseHandlers: undefined,
  18. keepalive: 'h5user_keepalive'
  19. },
  20. ctor() {
  21. this._requestCmds = {};
  22. this._requestModels = {};
  23. this._responseHandlers = {};
  24. },
  25. /**
  26. * 加载协议文件
  27. *
  28. * @author Wetion
  29. * @date 2019-03-22
  30. * @param {Array} protoPaths
  31. */
  32. loadProtoFiles(protoPaths) {
  33. if (SocketUtil.clientHost && SocketUtil.clientSender) {
  34. G.LogUtils.warn('Warn:SocketClient协议文件已经加载过');
  35. return;
  36. }
  37. if (!G.FuncUtils.isArray(protoPaths)) {
  38. G.LogUtils.warn('Warn:SocketClient加载协议文件参数不正确');
  39. return;
  40. }
  41. // 加载协议文件
  42. cc.loader.loadResArray(protoPaths, (err, assets) => {
  43. if (err) {
  44. G.LogUtils.error('Bug:SocketClient协议文件失败 ' + err);
  45. return;
  46. }
  47. // 做个异步处理
  48. let protos = {};
  49. assets.forEach(proto => {
  50. let schema = JSON.parse(proto.text);
  51. let data = sproto.createNew(new Uint8Array(schema));
  52. protos[proto._name] = data;
  53. });
  54. SocketUtil.clientHost = protos.socket_s2c.host();
  55. SocketUtil.clientSender = SocketUtil.clientHost.attach(protos.socket_c2s);
  56. G.LogUtils.log('Info:SocketClient协议文件加载成功');
  57. cc.game.emit('e_nwk_socket_sproto_done');
  58. // 释放资源
  59. for (const path of protoPaths) {
  60. cc.loader.releaseRes(path);
  61. }
  62. });
  63. },
  64. /**
  65. * 是否准备完成(发送消息的前提)
  66. *
  67. * @author Wetion
  68. * @date 2019-03-22
  69. * @returns {boolean}
  70. */
  71. isReadyDone() {
  72. return this._ws && this._ws.readyState === WebSocket.OPEN && SocketUtil.clientHost && SocketUtil.clientSender;
  73. },
  74. _isValidURLArgs(host, port) {
  75. return (G.FuncUtils.isIP(host) && G.FuncUtils.isPort(port) || G.FuncUtils.isDomain(host));
  76. },
  77. /**
  78. * 连接Socket
  79. *
  80. * @author Wetion
  81. * @date 2019-03-22
  82. * @param {String} host
  83. * @param {Number} port
  84. */
  85. connect(host, port) {
  86. if (!this._isValidURLArgs(host, port)) {
  87. G.LogUtils.error('Bug:SocketClient连接信息错误');
  88. return;
  89. }
  90. if (this._ws) {
  91. // 关闭旧连接
  92. this.close();
  93. }
  94. // 连接服务器
  95. this.host = host;
  96. this.port = port;
  97. let url = cc.js.formatStr('ws://%s:%s', host, port);
  98. if (G.FuncUtils.isIP(host)) {
  99. url = cc.js.formatStr('ws://%s:%s', host, port);
  100. } else {
  101. url = cc.js.formatStr('wss://%s', host);
  102. }
  103. url = cc.js.formatStr('ws://%s:%s', host, port);
  104. this._ws = new WebSocket(url);
  105. this._ws.binaryType = 'arraybuffer';
  106. this._ws.onopen = this._onConnect.bind(this);
  107. this._ws.onmessage = this._onMessage.bind(this);
  108. this._ws.onerror = this._onError.bind(this);
  109. this._ws.onclose = this._onClose.bind(this);
  110. },
  111. _send(protoName, info, session) {
  112. let requestBuffer = G.FuncUtils.uint8ToBuffer(SocketUtil.clientSender(protoName, info, session));
  113. this._ws.send(requestBuffer); // 发送数据
  114. },
  115. reconnect() {
  116. this.connect(this.host, this.port);
  117. },
  118. /**
  119. * 关闭Socket,不管处于什么状态
  120. */
  121. close() {
  122. if (!this._ws) {
  123. return;
  124. }
  125. this._ws.onopen = undefined;
  126. this._ws.onmessage = undefined;
  127. this._ws.onerror = undefined;
  128. this._ws.onclose = undefined;
  129. if (this._ws.readyState == WebSocket.CONNECTING || this._ws.readyState == WebSocket.OPEN) {
  130. this._ws.close();
  131. cc.game.emit('e_socket_on_closed', 'call_close');
  132. }
  133. this._ws = undefined;
  134. },
  135. /**
  136. * 客户端请求服务器信息
  137. *
  138. * @author Wetion
  139. * @date 2019-03-22
  140. * @param {String} protoName
  141. * @param {Object} info
  142. * @param {Function} responseHandler
  143. * @returns {Boolean} 是否成功发送
  144. */
  145. c2sRequest(protoName, info, responseHandler) {
  146. if (!this.isReadyDone()) {
  147. return;
  148. }
  149. this._session++;
  150. this._requestCmds[this._session] = protoName;
  151. this._requestModels[this._session] = info;
  152. this._responseHandlers[this._session] = responseHandler;
  153. if (protoName !== this.keepalive) {
  154. G.LogUtils.log('--> 网络请求 socket session:', this._session, 'proto name:', protoName, ' request info:', info);
  155. }
  156. this._send(protoName, info, this._session); // 发送数据
  157. return true;
  158. },
  159. c2sResponse(buffer) {
  160. let protoName = this._requestCmds[buffer.session];
  161. if (protoName !== this.keepalive) {
  162. G.LogUtils.log('<-- 网络返回 socket session:', buffer.session, 'proto name:', protoName, ' response info:', buffer.result);
  163. }
  164. let requestInfo = this._requestModels[buffer.session];
  165. let responseHandler = this._responseHandlers[buffer.session];
  166. if (responseHandler) {
  167. responseHandler({ requestInfo: requestInfo, responseInfo: buffer.result });
  168. }
  169. },
  170. s2cRequest(buffer) {
  171. G.LogUtils.log('<~~ 网络通知 socket pname:', buffer.pname, ' request info:', buffer.result);
  172. cc.game.emit(buffer.pname, buffer.result);
  173. },
  174. _onConnect(evt) {
  175. G.LogUtils.log('连接成功 ', evt);
  176. cc.game.emit('e_socket_on_connected');
  177. },
  178. _onMessage(evt) {
  179. // 解析数据
  180. let buffer = SocketUtil.clientHost.dispatch(G.FuncUtils.bufferToUint8(evt.data));
  181. if (buffer.type === 'REQUEST') {
  182. // 避免native释放buffer导致 object already destroyed
  183. buffer.result = G.FuncUtils.clone(buffer.result);
  184. this.s2cRequest(buffer);
  185. } else if (buffer.type === 'RESPONSE') {
  186. // 避免native释放buffer导致 object already destroyed
  187. buffer.result = G.FuncUtils.clone(buffer.result);
  188. this.c2sResponse(buffer);
  189. } else {
  190. G.LogUtils.warn('不能解析的类型', buffer);
  191. }
  192. },
  193. _onError(err) {
  194. G.LogUtils.log('连接错误:', err);
  195. if (this._ws) {
  196. // 因为onerror后会马上触发onclose
  197. // 避免发两次 e_socket_on_closed 消息
  198. this._ws.onopen = undefined;
  199. this._ws.onmessage = undefined;
  200. this._ws.onerror = undefined;
  201. this._ws.onclose = undefined;
  202. this._ws = undefined;
  203. }
  204. cc.game.emit('e_socket_on_closed', 'on_error');
  205. },
  206. _onClose(evt) {
  207. G.LogUtils.log('连接关闭:', evt);
  208. if (this._ws) {
  209. // 收到关闭消息后对当前连接进行清理
  210. this._ws.onopen = undefined;
  211. this._ws.onmessage = undefined;
  212. this._ws.onerror = undefined;
  213. this._ws.onclose = undefined;
  214. this._ws = undefined;
  215. }
  216. cc.game.emit('e_socket_on_closed', 'on_close');
  217. }
  218. });