/*-----------------------------------------------------+ * socket相关处理 * @author whjing2012@163.com +-----------------------------------------------------*/ const Proto = require( "proto_mate" ); const SocketSvr = require( "SocketService" ); const { SSL } = require( "config" ); // 心跳间隔&超时次数 const HEARTBEAT_ID = 1199; const HEARTBEAT_PERIOD = 10000; const HEARTBEAT_LIMIT = 2; // 数据类型 const 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 } ); const fmtNow = function() { let dt = new Date(); let date = `${ dt.getHours() }:${ dt.getMinutes() }:${ dt.getSeconds() }`; return date; }; const output = function( _content ) { if( window.nx ) { nx.info( `${ _content }` ); } else { cc.log( `${fmtNow()}${_content}` ); } }; const invoke = function( _fn ) { if( !!_fn && typeof _fn == 'function' ) { _fn.apply( null, Array.prototype.slice.call( arguments, 1 ) ); } }; const CliSocket = { getTime: function() { return this.diff_time + parseInt( cc.sys.now() / 1000 ); }, getMsTime: function() { return this.diff_time * 1000 + cc.sys.now(); }, setTime: function( time ) { this.diff_time = time - parseInt( cc.sys.now() / 1000 ); }, init: function() { 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() { // cc.log( "$心跳:开始" ); this.hb_caches = 0; this.bindCmd( HEARTBEAT_ID, this.on1199.bind( this ), "hb" ); this.net_connect.AttachBeartbeat( HEARTBEAT_PERIOD, () => { this.tickHeartBeat(); return this.structMsg( HEARTBEAT_ID, {} ); } ); }, // 心跳检查 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( data ) { // cc.log( "$心跳:有效回复, 当前:", this.hb_caches ); this.hb_caches = 0; }, // ====================================================================== // 连接处理 // ====================================================================== // 连接网络 connect: function( 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" ); let 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( _flag = "close" ) { output( cc.js.formatStr( "$网络:连接关闭... %s", this._curInfo() ) ); this.closeFlag = _flag; if( this.net_connect ) { this.net_connect.DisConnect(); } }, // 断开websocket breakws: function() { output( cc.js.formatStr( "$网络:连接断开... %s", this._curInfo() )); this.close(); }, // 发送消息 send: function( cmd, data ) { // 完整日志 output( cc.js.formatStr( "[send] >>>[%d]:%s", cmd, JSON.stringify( data || {} ) )); // 参数封装 let p_data = this.structMsg( cmd, data ); if( !p_data ) { return false; } return this.net_connect.Send( p_data ); }, // 协议监听 bindCmd: function( cmd, func, key ) { if( !func ) { cc.error( "$Socket:无效监听函数!", cmd, key ); return; } if( !this.listeners[ cmd ] ) { this.listeners[ cmd ] = []; } // 重复判定 let flag = key || "none"; if( CC_DEBUG ) { for( let i in this.listeners[ cmd ] ) { let 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( _cmd, _key ) { let arr = this.listeners[ _cmd ]; if( !arr || arr.length == 0 ) { return; } let flag = _key || "none"; let i = arr.length; while( i-- ) { if( arr[ i ].key == flag ) { arr.splice( i, 1 ); } } }, // 发送消息组装 structMsg: function( 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() { //这里不需要,网络层自驱动,如需特殊处理,打开即可 // // 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( 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( self, msg, data ) { self.doRecv( data, self ); }, // 事件:网络关闭 on_close: function( 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( self, e ) { cc.error( "$网络监听:错误... %s", self._curInfo() ); console.error( e ); // 回调 if( self.cbConnect ) { invoke( self.cbConnect, false, "TipConnectFailed" ); self.cbConnect = null; } }, onCmdCallback: function( cmd, data ) { if( !window.nx ) { return; } try { let arr = this.listeners[ cmd ]; if( arr && arr.length > 0 ) { arr.forEach( _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( _doing ) { this.connecting = _doing; }, // ====================================================================== // 设置连接状态 // ====================================================================== // 设置 setLinkFlag: function( _flag ) { if( !window.nx ) { return; } let flag = _flag || "close"; let 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( buffer, self ) { // 重置心跳包 this.check_time = 0; if( typeof ( buffer ) == "string" ) //服务器传过来的可能是字符串,判断是不是 { output( "[recv] =text= " + buffer ); return; } if( self.buffer == null ) { buffer = new Uint8Array( buffer ); } else { let a = Array.from( new Uint8Array( buffer ) ) let b = Array.from( new Uint8Array( self.buffer ) ) buffer = b.concat( a ); } this.unpackBuffer( buffer ); }, unpackBuffer: function( 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( 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 || {}, ( key, value ) => 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( 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( 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( 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( 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( bytes, num ) { var a = new Uint8Array( ( new Uint16Array( [ num ] ) ).buffer ); bytes.push( a[ 1 ] ); bytes.push( a[ 0 ] ); }, i32ToBytes: function( 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( _self ) { let txt = "无连接"; let self = _self || this; if( self.net_connect ) { let ip = self.net_connect.GetIp(); let port = self.net_connect.GetPort(); txt = cc.js.formatStr( "连接地址:%s,端口号: %d", ip, port ); } return txt; }, }; // 载入 const install = function() { CliSocket.init(); client.socket = CliSocket; }; // 导出 module.exports = { install };