const sproto = require('sproto'); const SocketUtil = require('./SocketUtil'); cc.Class({ name: 'SocketClient', properties: { _clientHost: undefined, _clientSender: undefined, _ws: undefined, _session: 0, _reconnectTimes: 0, reconnectMaxTimes: 3, _connectTimeoutId: undefined, connectTimeout: 10000, host: undefined, _requestCmds: undefined, _requestModels: undefined, _responseHandlers: undefined, keepalive: 'h5user_keepalive' }, ctor() { this._requestCmds = {}; this._requestModels = {}; this._responseHandlers = {}; }, /** * 加载协议文件 * * @author Wetion * @date 2019-03-22 * @param {Array} protoPaths */ loadProtoFiles(protoPaths) { if (SocketUtil.clientHost && SocketUtil.clientSender) { G.LogUtils.warn('Warn:SocketClient协议文件已经加载过'); return; } if (!G.FuncUtils.isArray(protoPaths)) { G.LogUtils.warn('Warn:SocketClient加载协议文件参数不正确'); return; } // 加载协议文件 cc.loader.loadResArray(protoPaths, (err, assets) => { if (err) { G.LogUtils.error('Bug:SocketClient协议文件失败 ' + err); return; } // 做个异步处理 let protos = {}; assets.forEach(proto => { let schema = JSON.parse(proto.text); let data = sproto.createNew(new Uint8Array(schema)); protos[proto._name] = data; }); SocketUtil.clientHost = protos.socket_s2c.host(); SocketUtil.clientSender = SocketUtil.clientHost.attach(protos.socket_c2s); G.LogUtils.log('Info:SocketClient协议文件加载成功'); cc.game.emit('e_nwk_socket_sproto_done'); // 释放资源 for (const path of protoPaths) { cc.loader.releaseRes(path); } }); }, /** * 是否准备完成(发送消息的前提) * * @author Wetion * @date 2019-03-22 * @returns {boolean} */ isReadyDone() { return this._ws && this._ws.readyState === WebSocket.OPEN && SocketUtil.clientHost && SocketUtil.clientSender; }, _isValidURLArgs(host, port) { return (G.FuncUtils.isIP(host) && G.FuncUtils.isPort(port) || G.FuncUtils.isDomain(host)); }, /** * 连接Socket * * @author Wetion * @date 2019-03-22 * @param {String} host * @param {Number} port */ connect(host, port) { if (!this._isValidURLArgs(host, port)) { G.LogUtils.error('Bug:SocketClient连接信息错误'); return; } if (this._ws) { // 关闭旧连接 this.close(); } // 连接服务器 this.host = host; this.port = port; let url = cc.js.formatStr('ws://%s:%s', host, port); if (G.FuncUtils.isIP(host)) { url = cc.js.formatStr('ws://%s:%s', host, port); } else { url = cc.js.formatStr('wss://%s', host); } url = cc.js.formatStr('ws://%s:%s', host, port); this._ws = new WebSocket(url); this._ws.binaryType = 'arraybuffer'; this._ws.onopen = this._onConnect.bind(this); this._ws.onmessage = this._onMessage.bind(this); this._ws.onerror = this._onError.bind(this); this._ws.onclose = this._onClose.bind(this); }, _send(protoName, info, session) { let requestBuffer = G.FuncUtils.uint8ToBuffer(SocketUtil.clientSender(protoName, info, session)); this._ws.send(requestBuffer); // 发送数据 }, reconnect() { this.connect(this.host, this.port); }, /** * 关闭Socket,不管处于什么状态 */ close() { if (!this._ws) { return; } this._ws.onopen = undefined; this._ws.onmessage = undefined; this._ws.onerror = undefined; this._ws.onclose = undefined; if (this._ws.readyState == WebSocket.CONNECTING || this._ws.readyState == WebSocket.OPEN) { this._ws.close(); cc.game.emit('e_socket_on_closed', 'call_close'); } this._ws = undefined; }, /** * 客户端请求服务器信息 * * @author Wetion * @date 2019-03-22 * @param {String} protoName * @param {Object} info * @param {Function} responseHandler * @returns {Boolean} 是否成功发送 */ c2sRequest(protoName, info, responseHandler) { if (!this.isReadyDone()) { return; } this._session++; this._requestCmds[this._session] = protoName; this._requestModels[this._session] = info; this._responseHandlers[this._session] = responseHandler; if (protoName !== this.keepalive) { G.LogUtils.log('--> 网络请求 socket session:', this._session, 'proto name:', protoName, ' request info:', info); } this._send(protoName, info, this._session); // 发送数据 return true; }, c2sResponse(buffer) { let protoName = this._requestCmds[buffer.session]; if (protoName !== this.keepalive) { G.LogUtils.log('<-- 网络返回 socket session:', buffer.session, 'proto name:', protoName, ' response info:', buffer.result); } let requestInfo = this._requestModels[buffer.session]; let responseHandler = this._responseHandlers[buffer.session]; if (responseHandler) { responseHandler({ requestInfo: requestInfo, responseInfo: buffer.result }); } }, s2cRequest(buffer) { G.LogUtils.log('<~~ 网络通知 socket pname:', buffer.pname, ' request info:', buffer.result); cc.game.emit(buffer.pname, buffer.result); }, _onConnect(evt) { G.LogUtils.log('连接成功 ', evt); cc.game.emit('e_socket_on_connected'); }, _onMessage(evt) { // 解析数据 let buffer = SocketUtil.clientHost.dispatch(G.FuncUtils.bufferToUint8(evt.data)); if (buffer.type === 'REQUEST') { // 避免native释放buffer导致 object already destroyed buffer.result = G.FuncUtils.clone(buffer.result); this.s2cRequest(buffer); } else if (buffer.type === 'RESPONSE') { // 避免native释放buffer导致 object already destroyed buffer.result = G.FuncUtils.clone(buffer.result); this.c2sResponse(buffer); } else { G.LogUtils.warn('不能解析的类型', buffer); } }, _onError(err) { G.LogUtils.log('连接错误:', err); if (this._ws) { // 因为onerror后会马上触发onclose // 避免发两次 e_socket_on_closed 消息 this._ws.onopen = undefined; this._ws.onmessage = undefined; this._ws.onerror = undefined; this._ws.onclose = undefined; this._ws = undefined; } cc.game.emit('e_socket_on_closed', 'on_error'); }, _onClose(evt) { G.LogUtils.log('连接关闭:', evt); if (this._ws) { // 收到关闭消息后对当前连接进行清理 this._ws.onopen = undefined; this._ws.onmessage = undefined; this._ws.onerror = undefined; this._ws.onclose = undefined; this._ws = undefined; } cc.game.emit('e_socket_on_closed', 'on_close'); } });