Files

664 lines
20 KiB
JavaScript
Raw Permalink Normal View History

2026-05-23 22:10:14 +08:00
/*-----------------------------------------------------+
* 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 };