664 lines
20 KiB
JavaScript
664 lines
20 KiB
JavaScript
/*-----------------------------------------------------+
|
|
* 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 };
|
|
|