"use strict"; cc._RF.push(module, 'a4cf66+FXxG5rd4h8TtCXqZ', 'socket'); // Scripts/client/socket/socket.js "use strict"; /*-----------------------------------------------------+ * socket相关处理 * @author whjing2012@163.com +-----------------------------------------------------*/ var Proto = require("proto_mate"); var SocketSvr = require("SocketService"); var _require = require("config"), SSL = _require.SSL; // 心跳间隔&超时次数 var HEARTBEAT_ID = 1199; var HEARTBEAT_PERIOD = 10000; var HEARTBEAT_LIMIT = 2; // 数据类型 var DataType = cc.Enum({ None: 0, Int8: 1, UInt8: 2, Int16: 3, UInt16: 4, Int32: 5, UInt32: 6, String: 7, Byte: 8, Array: 9, Float: 10, Int64: 11, UInt64: 12 }); var fmtNow = function fmtNow() { var dt = new Date(); var date = dt.getHours() + ":" + dt.getMinutes() + ":" + dt.getSeconds(); return date; }; var output = function output(_content) { if (window.nx) { nx.info("" + _content); } else { cc.log("" + fmtNow() + _content); } }; var invoke = function invoke(_fn) { if (!!_fn && typeof _fn == 'function') { _fn.apply(null, Array.prototype.slice.call(arguments, 1)); } }; var CliSocket = { getTime: function getTime() { return this.diff_time + parseInt(cc.sys.now() / 1000); }, getMsTime: function getMsTime() { return this.diff_time * 1000 + cc.sys.now(); }, setTime: function setTime(time) { this.diff_time = time - parseInt(cc.sys.now() / 1000); }, init: function init() { this.net_connect = null; this.buffer = null; this.listeners = {}; this.msg_list = []; this.diff_time = 0; this.connecting = false; this.check_time = 0; //初始化网络接口 SocketSvr.InitWebSocket(WebSocket); //创建一个连接实例: 支持wss/ws this.net_connect = SocketSvr.CreateConnect(SSL); // 初始化网络事件 this.net_connect.Init(this, this.on_open, this.on_message, this.on_close, this.on_error); // 网络协议转换,当前不使用 //this.net_connect.AttachMessage(fn_on_receive,fn_on_send); // 注册心跳 this.readyHeartBeat(); if (!Uint8Array.prototype.slice) { Uint8Array.prototype.slice = Array.prototype.slice; } }, // ====================================================================== // 心跳处理 // ====================================================================== // 心跳监听 readyHeartBeat: function readyHeartBeat() { var _this = this; // cc.log( "$心跳:开始" ); this.hb_caches = 0; this.bindCmd(HEARTBEAT_ID, this.on1199.bind(this), "hb"); this.net_connect.AttachBeartbeat(HEARTBEAT_PERIOD, function () { _this.tickHeartBeat(); return _this.structMsg(HEARTBEAT_ID, {}); }); }, // 心跳检查 tickHeartBeat: function tickHeartBeat() { // cc.log( "$心跳:触发, 次数: %s -> %s", this.hb_caches, this.hb_caches + 1 ); // let old = nx.bridge.vget( "connFlag" ); // if( old != "good" ) { // return; // } // 缓存未回复次数 this.hb_caches += 1; if (this.hb_caches >= HEARTBEAT_LIMIT) { cc.log("$心跳:超时,主动断开网络!"); this.breakws(); } }, // 心跳 5发1收 on1199: function on1199(data) { // cc.log( "$心跳:有效回复, 当前:", this.hb_caches ); this.hb_caches = 0; }, // ====================================================================== // 连接处理 // ====================================================================== // 连接网络 connect: function connect(host, port, ws, _cb) { output(cc.js.formatStr("$网络:连接开始... 地址:%s,端口:%s,SSL:%s", host, port, ws || "ws")); this.buffer = null; this.hb_caches = 0; this.closeFlag = ""; // 连接开始 this.net_connect.SetSSL(ws == "wss"); var code = this.net_connect.Connect(host, port); if (code == SocketSvr.ErrCode().SUCC) { this.setConnecting(true); this.setLinkFlag("linking"); this.cbConnect = _cb; return; } // 重复连接 if (code == SocketSvr.ErrCode().ERR_ALREADY_CONNECTED) { invoke(_cb, true, "repeat"); this.setConnecting(true); this.setLinkFlag("good"); this.cbConnect = null; return; } // 连接失败 invoke(_cb, false, "TipConnectFailed"); this.setConnecting(false); this.setLinkFlag("close"); this.cbConnect = null; }, // 关闭网络 close: function close(_flag) { if (_flag === void 0) { _flag = "close"; } output(cc.js.formatStr("$网络:连接关闭... %s", this._curInfo())); this.closeFlag = _flag; if (this.net_connect) { this.net_connect.DisConnect(); } }, // 断开websocket breakws: function breakws() { output(cc.js.formatStr("$网络:连接断开... %s", this._curInfo())); this.close(); }, // 发送消息 send: function send(cmd, data) { // 完整日志 output(cc.js.formatStr("[send] >>>[%d]:%s", cmd, JSON.stringify(data || {}))); // 参数封装 var p_data = this.structMsg(cmd, data); if (!p_data) { return false; } return this.net_connect.Send(p_data); }, // 协议监听 bindCmd: function bindCmd(cmd, func, key) { if (!func) { cc.error("$Socket:无效监听函数!", cmd, key); return; } if (!this.listeners[cmd]) { this.listeners[cmd] = []; } // 重复判定 var flag = key || "none"; if (CC_DEBUG) { for (var i in this.listeners[cmd]) { var tm = this.listeners[cmd][i]; if (tm && tm.key == flag) { cc.error("$Socket:重复监听!", cmd, flag); return; } } } // 加入列表 this.listeners[cmd].push({ cb: func, key: flag }); }, // 协议监听解除 unbindCmd: function unbindCmd(_cmd, _key) { var arr = this.listeners[_cmd]; if (!arr || arr.length == 0) { return; } var flag = _key || "none"; var i = arr.length; while (i--) { if (arr[i].key == flag) { arr.splice(i, 1); } } }, // 发送消息组装 structMsg: function structMsg(cmd, data) { try { var bytes = this.packData(cmd, data); var buffer = new ArrayBuffer(bytes.length + 4); var view = new DataView(buffer); view.setUint32(0, bytes.length); for (var i = 0; i < bytes.length; i++) { view.setUint8(i + 4, bytes[i]); } return view.buffer; } catch (error) { cc.error("[send] send_msg_error:" + error.message, error.stack); if (error && cc.sys.isNative && window && window.__errorHandler) { window.__errorHandler(error.message, "", "", error.stack); } return null; } }, handleMsg: function handleMsg() { //这里不需要,网络层自驱动,如需特殊处理,打开即可 // // if( this.msg_list.length == 0 ) return; // for( var i = 0; i < 5; i++ ) { // var msg = this.msg_list.shift(); // this.onCmdCallback( msg.cmd, msg.data ); // if( this.msg_list.length == 0 ) return; // } }, // ====================================================================== // 监听处理 // ====================================================================== // 事件:网络打开准备就绪 on_open: function on_open(self, is_reconnect, e) { cc.log("$网络监听:已打开... %s", self._curInfo()); self.setConnecting(false); gcore.GlobalEvent.fire_x(gcore.GlobalEvent, gcore.GlobalEvent.EVT_SOCKET_CONNECT); // 视图标记 self.setLinkFlag("good"); // 回调 if (self.cbConnect) { invoke(self.cbConnect, true); self.cbConnect = null; } }, // 事件:网络接收消息 on_message: function on_message(self, msg, data) { self.doRecv(data, self); }, // 事件:网络关闭 on_close: function on_close(self) { cc.log("$网络监听:已关闭... %s", self._curInfo()); self.setConnecting(false); gcore.GlobalEvent.fire_x(gcore.GlobalEvent, gcore.GlobalEvent.EVT_SOCKET_DISCONNECT, self.closeFlag); // 视图标记 self.setLinkFlag(self.closeFlag); return true; }, // 事件:网络错误 on_error: function on_error(self, e) { cc.error("$网络监听:错误... %s", self._curInfo()); console.error(e); // 回调 if (self.cbConnect) { invoke(self.cbConnect, false, "TipConnectFailed"); self.cbConnect = null; } }, onCmdCallback: function onCmdCallback(cmd, data) { if (!window.nx) { return; } try { var arr = this.listeners[cmd]; if (arr && arr.length > 0) { arr.forEach(function (_hook) { if (_hook.cb) { invoke(_hook.cb, data, cmd); } }); } } catch (error) { cc.error("handle_msg_error:" + cmd + ", err=" + error.message + ", data=" + data, error, error.stack); if (error && cc.sys.isNative && window && window.__errorHandler) { window.__errorHandler(error.message, "", "", error.stack); } } }, // 设置连接状态 setConnecting: function setConnecting(_doing) { this.connecting = _doing; }, // ====================================================================== // 设置连接状态 // ====================================================================== // 设置 setLinkFlag: function setLinkFlag(_flag) { if (!window.nx) { return; } var flag = _flag || "close"; var old = nx.bridge.vget("connFlag"); if (old == flag) { return; } output(cc.js.formatStr("$SOCKET: %s -> %s", old, flag)); nx.bridge.vset("connFlag", flag); }, // ====================================================================== // 数据处理 // ====================================================================== doRecv: function doRecv(buffer, self) { // 重置心跳包 this.check_time = 0; if (typeof buffer == "string") //服务器传过来的可能是字符串,判断是不是 { output("[recv] =text= " + buffer); return; } if (self.buffer == null) { buffer = new Uint8Array(buffer); } else { var a = Array.from(new Uint8Array(buffer)); var b = Array.from(new Uint8Array(self.buffer)); buffer = b.concat(a); } this.unpackBuffer(buffer); }, unpackBuffer: function unpackBuffer(buffer) { if (buffer.length < 6) { this.buffer = buffer; return; } var dataview = new DataView(new Uint8Array(buffer).buffer); var len = dataview.getUint32(0, false); //cc.log("======= " + len); if (len > 80000) { cc.error("[recv] data_to_long:" + len); this.net_connect.DisConnec(); } var data_len = buffer.length; if (len + 4 > data_len) { return; } var cmd = 0; try { cmd = dataview.getUint16(4, false); this.unpackData(dataview, cmd, buffer, len); } catch (error) { cc.error("[recv] " + cmd + ": unpackData_error:" + error.message, buffer, error.stack); if (error && cc.sys.isNative && window && window.__errorHandler) { window.__errorHandler(error.message, "", "", error.stack); } } if (data_len > len + 4) { // cc.log( "data_length==", cmd, len ); this.unpackBuffer(buffer.slice(len + 4)); } else { this.buffer = null; // if( cmd != HEARTBEAT_ID ) { // cc.error( "协议空内容:" + cmd ); // } } }, unpackData: function unpackData(dataview, cmd, buffer, len) { if (!Proto.recv.hasOwnProperty(cmd)) { throw new Error("[recv] unpackData查找不到协议定义:" + cmd); } var data = {}; var mate = Proto.recv[cmd]; this.unpackData_(dataview, buffer, data, mate, 6, len); // 屏蔽心跳 if (cmd != '1199') { output(cc.js.formatStr("[recv] <<<[%d]:%s", cmd, JSON.stringify(data || {}, function (key, value) { return typeof value === 'bigint' ? value.toString() : value; } // return everything else unchanged ))); } if (!this.listeners[cmd]) { cc.warn("[recv] 协议处理函数未定义:" + cmd); return; } this.onCmdCallback(cmd, data); return data; }, unpackData_: function unpackData_(dataview, buffer, data, mate, pos, len) { for (var i = 0, n = mate.length; i < n; i++) { var field = mate[i]; //cc.log("field=" +field.s +" i=" + i + " t=" + field.t + " n=" +n + " pos=" + pos); switch (field.t) { case DataType.Int8: { data[field.s] = dataview.getInt8(pos, false); pos += 1; } break; case DataType.UInt8: { data[field.s] = dataview.getUint8(pos, false); pos += 1; } break; case DataType.Int16: { data[field.s] = dataview.getInt16(pos, false); pos += 2; } break; case DataType.UInt16: { data[field.s] = dataview.getUint16(pos, false); pos += 2; } break; case DataType.Int32: { data[field.s] = dataview.getInt32(pos, false); pos += 4; } break; case DataType.UInt32: { data[field.s] = dataview.getUint32(pos, false); pos += 4; } break; case DataType.String: { var s_len = dataview.getUint16(pos, false); pos += 2; var unit8Arr = new Uint8Array(buffer.slice(pos, pos + s_len)); var encodedString = String.fromCharCode.apply(null, unit8Arr); data[field.s] = decodeURIComponent(escape(encodedString)); //没有这一步中文会乱码 pos += s_len; } break; case DataType.Byte: { var s_len = dataview.getUint32(pos, false); pos += 4; data[field.s] = buffer.slice(pos, pos + s_len); //没有这一步中文会乱码 pos += s_len; } break; case DataType.Array: { var s_len = dataview.getUint16(pos, false); pos += 2; var arr = []; data[field.s] = arr; for (var j = 0; j < s_len; j++) { var data1 = {}; arr[j] = data1; pos = this.unpackData_(dataview, buffer, data1, field.f, pos, len); } } break; case DataType.Float: { data[field.s] = dataview.getFloat64(pos, false); pos += 8; } break; case DataType.Int64: { data[field.s] = dataview.getInt64(pos, false); pos += 8; } break; case DataType.UInt64: { data[field.s] = dataview.getUint64(pos, false); pos += 8; } break; default: { cc.warn("无效数据类型:" + field.t); } break; } //cc.log("unpack_data:" + field.s + ", val=" + data[field.s]); } return pos; }, packData: function packData(cmd, data) { if (!Proto.send.hasOwnProperty(cmd)) { throw new Error("unpackData查找不到协议定义:" + cmd); } // cc.log( "pack_data2:" + cmd ); var mate = Proto.send[cmd]; var bytes = []; this.i16ToBytes(bytes, cmd); this.packData_(bytes, mate, data); return bytes; }, packData_: function packData_(bytes, mate, data) { for (var i = 0, n = mate.length; i < n; i++) { var field = mate[i]; var val = data[field.s]; // cc.log("pack_data2:" + field.s + ", val=" + val); switch (field.t) { case 1: case 2: bytes.push(new Uint8Array([val])); break; case 3: case 4: this.i16ToBytes(bytes, val); break; case 5: case 6: this.i32ToBytes(bytes, val); break; case 7: this.strToBytes(bytes, val); break; case 8: var s_len = val.length; this.i32ToBytes(bytes, s_len); for (var j = 0; j < s_len; j++) { bytes.push(val[j]); } break; case 9: var s_len = val.length; // cc.log("pack_data2:" + field.s + ", val=" + val + ", len:" + s_len); this.i16ToBytes(bytes, s_len); for (var j = 0; j < s_len; j++) { if (val[0] == null && val.length > 0) { j = j + 1; } this.packData_(bytes, field.f, val[j]); } break; } } }, strToBytes: function strToBytes(bytes, string) { var str = unescape(encodeURIComponent(string)); var len = str.length; this.i16ToBytes(bytes, len); for (var i = 0; i < len; i++) { bytes.push(str.charCodeAt(i)); } }, i16ToBytes: function i16ToBytes(bytes, num) { var a = new Uint8Array(new Uint16Array([num]).buffer); bytes.push(a[1]); bytes.push(a[0]); }, i32ToBytes: function i32ToBytes(bytes, num) { var a = new Uint8Array(new Uint32Array([num]).buffer); bytes.push(a[3]); bytes.push(a[2]); bytes.push(a[1]); bytes.push(a[0]); }, // 格式化当前连接信息 _curInfo: function _curInfo(_self) { var txt = "无连接"; var self = _self || this; if (self.net_connect) { var ip = self.net_connect.GetIp(); var port = self.net_connect.GetPort(); txt = cc.js.formatStr("连接地址:%s,端口号: %d", ip, port); } return txt; } }; // 载入 var install = function install() { CliSocket.init(); client.socket = CliSocket; }; // 导出 module.exports = { install: install }; cc._RF.pop();