Files
fc/dev/project/assets/Scripts/client/socket/SocketService.js
T

669 lines
15 KiB
JavaScript
Raw Normal View History

2026-05-23 22:10:14 +08:00
/**
* @author: kevin
* @dateTime: 2018-06-15 10:52:51
* @description: 封装socket连接(websocket)
* @copyright: Copyright(c) 2018 League_h5
* rfc6455#section-7.4
* close code: 1006,1005,1004
*/
// net error code
const ErrorCode = {
SUCC: 0, // 成功
FAIL: 1, // 失败
ERR_ALREADY_CONNECTED: 2, // 已经连接
FAIL_NOT_SUPPORT: 3, // 不支持
};
// net status(ws)
const NetStatus = {
UNKNOWN: 0, // 未知,尚未连接,或者已经断开
CONNECTING: 1, // 连接中
CONNECTED: 2, // 已经连接
};
// for connect pool
var this_connect_pool = [];
// for WebSocket
var WebSocketClass = null;
/*
@method: check_time_num10(i)
@desc: 检测时间数字,小于10前面加0
@return: [string]
*/
function check_time_num10( i ) {
if( i < 10 ) {
i = "0" + i;
}
return i;
}
/*
@method: get_str_curr_time()
@desc: 当前时间,格式:11:04:26
@return: [string]
*/
function get_str_curr_time() {
var today = new Date();
return check_time_num10( today.getHours() ) + ":" + check_time_num10( today.getMinutes() ) + ":" + check_time_num10( today.getSeconds() );
}
/*
@method: get_str_curr_datetime()
@desc: 当前日期时间,格式:2022-03-01 11:04:26
@return: [string]
*/
function get_str_curr_datetime() {
var today = new Date();
return today.getFullYear() + "-" + check_time_num10( today.getMonth() + 1 ) + "-" + check_time_num10( today.getDate() ) + " " + check_time_num10( today.getHours() ) + ":" + check_time_num10( today.getMinutes() ) + ":" + check_time_num10( today.getSeconds() );
}
/*
@interface: SocketConnect
@desc: for socket connect
@param: [boolean]is_ssl 是否启用ssl加密
*/
function SocketConnect( is_ssl ) {
this.is_ssl = !!is_ssl;
//是否重连
this.ip = '127.0.0.1';
this.port = 0;
this.ws = null;
this.status = NetStatus.UNKNOWN;
// 通知事件
this.call_self = null;
this.fn_on_open = null;
this.fn_on_message = null;
this.fn_on_close = null;
this.fn_on_error = null;
// 消息处理函数
this.fn_on_receive = null;
this.fn_on_send = null;
// 队列
this.send_msg_queue = [];
// 心跳
this.heart_beat_handle = null;
this.fn_heart_beat_ms = 0;
this.fn_heart_beat_msg = null;
}
/*
@method: SetSSL(is_ssl=false)
@desc: 设置ssl
@param: [boolean]is_ssl 是否启用ssl加密
@return: [boolean]
*/
SocketConnect.prototype.SetSSL = function( is_ssl ) {
if( is_ssl == null ) {
is_ssl = false;
}
this.is_ssl = is_ssl;
return true;
}
/*
@method: GetStatus()
@desc: 返回状态
@return: [boolean]
*/
SocketConnect.prototype.GetStatus = function() {
return this.status;
}
/*
@method: CanSendMsg()
@desc: 是否可发送消息,连接中或者重连也开放(开启发送池)
@return: [boolean]
*/
SocketConnect.prototype.CanSendMsg = function() {
if( this.status == NetStatus.CONNECTED ||
this.status == NetStatus.CONNECTING ) {
return true;
}
return false;
}
/*
@method: GetIp()
@desc: 返回: ip
@return: [string]
*/
SocketConnect.prototype.GetIp = function() {
return this.ip;
};
/*
@method: GetPort()
@desc: 返回: port
@return: [int]
*/
SocketConnect.prototype.GetPort = function() {
return this.port;
};
/*
@method: Init(call_self,fn_on_open,fn_on_message,fn_on_close,fn_on_error)
@desc: 初始化,附加处理函数
@param: [object]call_self 操作句柄
@param: [fn]fn_on_open on open,原型: function(self,is_reconnect,err)
@param: [fn]fn_on_message on message,原型:function(self,object,data)
@param: [fn]fn_on_close on close,原型:function(self,need_reconnect,err)
@param: [fn]fn_on_error on error,原型:function(self,err)
@return: [boolean]
*/
SocketConnect.prototype.Init = function( call_self, fn_on_open, fn_on_message, fn_on_close, fn_on_error ) {
this.call_self = call_self;
this.fn_on_open = fn_on_open;
this.fn_on_message = fn_on_message;
this.fn_on_close = fn_on_close;
this.fn_on_error = fn_on_error;
return true;
};
/*
@method: AttachMessage(fn_on_receive=null,fn_on_send=null)
@desc: 附加消息处理函数
@param: [fn]fn_on_receive on receive,原型: function(data,function(is_success,data,dst_data){})
@param: [fn]fn_on_send on send,原型:function(data,function(is_success,dst_data){})
@return: [boolean]
*/
SocketConnect.prototype.AttachMessage = function( fn_on_receive, fn_on_send ) {
this.fn_on_receive = fn_on_receive;
this.fn_on_send = fn_on_send;
return true;
};
/*
@method: AttachBeartbeat(fn_heart_beat_ms=0,fn_heart_beat_msg=null)
@desc: 启用服务器客户端心跳
@param: [int]fn_heart_beat_ms 心跳时间,单位ms0表示不启用
@param: [fn]fn_heart_beat_msg on send,原型:function(call_self)
@return: [boolean]
*/
SocketConnect.prototype.AttachBeartbeat = function( fn_heart_beat_ms, fn_heart_beat_msg ) {
if( fn_heart_beat_ms == null ) {
fn_heart_beat_ms = 0;
}
this.fn_heart_beat_ms = fn_heart_beat_ms;
this.fn_heart_beat_msg = fn_heart_beat_msg;
if( this.fn_heart_beat_msg == null ) {
this.fn_heart_beat_ms = 0;
}
return true;
};
/*
@method: Release()
@desc: 释放
@return: [boolean]
*/
SocketConnect.prototype.Release = function() {
this.ReleseHeart_Beat();
this.fn_heart_beat_ms = 0;
this.fn_heart_beat_msg = null;
this.status = NetStatus.UNKNOWN;
this.ws = null;
this.fn_on_open = null;
this.fn_on_message = null;
this.fn_on_close = null;
this.fn_on_error = null;
this.fn_on_receive = null;
this.fn_on_send = null;
this.send_msg_queue = [];
return true;
};
/*
@method: ReleseHeart_Beat()
@desc: 释放心跳
@return: [boolean]
*/
SocketConnect.prototype.ReleseHeart_Beat = function() {
if( this.heart_beat_handle != null ) {
clearTimeout( this.heart_beat_handle );
this.heart_beat_handle = null;
}
return true;
};
/*
@method: StartHeart_Beat()
@desc: 启动心跳
@return: [boolean]
*/
SocketConnect.prototype.StartHeart_Beat = function() {
this.ReleseHeart_Beat();
var self = this;
self.heart_beat_handle = setTimeout( function() {
self.CallHeart_Beat( self );
}, self.fn_heart_beat_ms );
return true;
};
/*
@method: CallHeart_Beat(self)
@desc: 回调心跳
@return: [boolean]
*/
SocketConnect.prototype.CallHeart_Beat = function( self ) {
self.heart_beat_handle = null;
var p_msg = self.fn_heart_beat_msg( self.call_self );
if( p_msg == null ) {
return false;
}
let ret = self.Send( p_msg );
if( !ret ) {
return false;
}
self.heart_beat_handle = setTimeout( function() {
self.CallHeart_Beat( self );
}, self.fn_heart_beat_ms );
return true;
};
/*
@method: Connect(ip=null,port=null)
@desc: 连接服务器
@param: [string]ip
@param: [int]port
@return: [int] ErrorCode.SUCC;
*/
SocketConnect.prototype.Connect = function( ip, port ) {
if( ip == null || port == null ) {
return ErrorCode.FAIL;
}
// 重复连接
if( this.status == NetStatus.CONNECTED && this.ip == ip && this.port == port ) {
console.log( '[ws_connect] 重复连接,忽略本次操作, ip=%s,port=%d', ip, port );
return ErrorCode.ERR_ALREADY_CONNECTED;
}
var self = this;
self.ws = null;
self.DisConnect();
self.ip = ip;
self.port = parseInt( port );
if( self.port <= 0 ) {
console.log( '[ws_connect] invalid, ip=%s,port=%d', ip, port );
return ErrorCode.FAIL;
}
// 开始连接
self.status = NetStatus.CONNECTING;
var remote_url = "";
if( self.is_ssl ) {
// remote_url = "wss://"+self.ip+":"+self.port+ "/websocket";//旧版本
remote_url = "wss://" + self.ip + ":443/wss" + self.port;//新版本
}
else {
remote_url = "ws://" + self.ip + ":" + self.port + "/websocket";
}
if( WebSocketClass == null ) {
console.log( '[ws_connect] not support, ip=%s,port=%d', ip, port );
self.status = NetStatus.UNKNOWN;
return ErrorCode.FAIL_NOT_SUPPORT;
}
self.ws = new WebSocketClass( remote_url );
if( self.ws == null ) {
console.log( '[ws_connect] failure, ip=%s,port=%d', ip, port );
self.status = NetStatus.UNKNOWN;
return ErrorCode.FAIL;
}
self.ws.binaryType = "arraybuffer";
// 连接成功
self.ws.onopen = function( e ) {
self.msg_queue = [];
if( self.queue_ticker ) {
clearInterval(self.queue_ticker);
self.queue_ticker = null;
}
self.status = NetStatus.CONNECTED;
if( self.fn_on_open != null ) {
self.fn_on_open( self.call_self, false, e );
}
//启动心跳
if( self.fn_heart_beat_ms > 0 ) {
self.StartHeart_Beat();
}
// //延迟发送
self.Try_Delay();
};
// 消息接收
self.ws.onmessage = function( e ) {
if( !self.fn_on_message ) {
return;
}
if( !self.fn_on_receive ) {
self.fn_on_message( self.call_self, e.data, e.data );
return;
}
self.fn_on_receive( e.data, function( is_success, s_data, p_data ) {
if( !is_success ) {
console.error( "[%s][recv] is failure(data)", get_str_curr_time() );
return false;
}
self.fn_on_message( self.call_self, p_data, s_data );
return true;
} );
};
// 关闭
self.ws.onclose = function( e ) {
self.ws = null;
self.status = NetStatus.UNKNOWN;
self.msg_queue = [];
if( self.queue_ticker ) {
clearInterval(self.queue_ticker);
self.queue_ticker = null;
}
if( self.fn_on_close ) {
self.fn_on_close( self.call_self, false, e );
}
return true;
};
// 错误
self.ws.onerror = function( e ) {
self.DisConnect();
if( self.fn_on_error ) {
self.fn_on_error( self.call_self, e );
}
};
console.log( '[ws_connect] error, ip=%s,port=%d', ip, port, ErrorCode.SUCC );
return ErrorCode.SUCC;
}
/*
@method: ReConnect()
@desc: 重新连接
@return: [int] ErrorCode.SUCC;
*/
SocketConnect.prototype.ReConnect = function() {
if( this.port <= 0 ) {
console.log( '[ws_reconnect] failure, ip=%s,port=%d', this.ip, this.port );
return ErrorCode.FAIL;
}
return this.Connect( this.ip, this.port );
};
/*
@method: DisConnect()
@desc: 断开连接
@return: [boolean]
*/
SocketConnect.prototype.DisConnect = function() {
this.status = NetStatus.UNKNOWN;
this.send_msg_queue = [];
this.ReleseHeart_Beat();
if( this.ws != null ) {
this.ws.close();
this.ws = null;
}
return true;
};
/*
@method: Send(data)
@desc: 发送消息
@param: [var]data 发送报文
@return: [boolean]
*/
SocketConnect.prototype.Send = function( data ) {
if( data == null || this.status != NetStatus.CONNECTED ) {
console.warn( "$Socket:发送失败..a")
return false;
}
if( !this.ws || this.ws.readyState != WebSocketClass.OPEN ) {
console.warn( "$Socket:发送失败..b")
return false;
}
this.msg_queue.push( data );
if( !this.queue_ticker ) {
this.queue_ticker = setInterval( () => {
let data = this.msg_queue.shift();
if( this.ws && data ) {
this.ws.send( data );
}
}, 20 );
}
// this.Try_Send( data );
return true;
};
// SocketConnect.prototype.Send = function( data ) {
// if( data == null ) {
// return false;
// }
// if( this.status != NetStatus.CONNECTED ) {
// if( this.status == NetStatus.CONNECTING ) {
// this.Send_Delay( data );
// return true;
// }
// return false;
// }
// if( this.ws == null ) {
// this.Send_Delay( data );
// this.DisConnect();
// return true;
// }
// if( this.ws.readyState === WebSocketClass.OPEN ) {
// this.Try_Send( data );
// }
// else {
// this.Send_Delay( data );
// // this.DisConnect();
// }
// return true;
// };
//丢弃处理,否则造成内存膨胀,导致内存溢出
SocketConnect.prototype.Send_Delay = function( data ) {
if( this.send_msg_queue.length >= 10000 ) {
var p_data = this.send_msg_queue.pop();
p_data = null;
}
this.send_msg_queue.unshift( data );
console.log( "[%s][send_delay] size =%d", this.send_msg_queue.length );
return true;
}
SocketConnect.prototype.Try_Send = function( data ) {
if( this.send_msg_queue.length <= 0 ) {
if( data != null ) {
this.Try_One_Send( data );
}
return true;
}
var p_data = null;
if( data != null ) {
this.send_msg_queue.unshift( data );
}
for( var i = 0; i < this.send_msg_queue.length; i++ ) {
p_data = this.send_msg_queue.pop();
if( p_data == null ) {
continue;
}
this.Try_One_Send( p_data );
}
return true;
}
SocketConnect.prototype.Try_One_Send = function( data ) {
if( this.fn_on_send == null ) {
if( this.ws != null ) {
// console.log("[%s][send][0] is succ(data) fn_on_send 1",get_str_curr_time());
this.ws.send( data );
}
else {
// console.error("[%s][send][0] is failure(data)",get_str_curr_time());
}
}
else {
var self = this;
self.fn_on_send( data, function( is_success, p_data ) {
if( is_success ) {
if( self.ws != null ) {
// console.log("[%s][send][1] is succ(data) fn_on_send 2",get_str_curr_time());
self.ws.send( p_data );
}
}
else {
// console.error("[%s][send][1] is failure(data)",get_str_curr_time());
}
} );
}
data = null;
return true;
}
SocketConnect.prototype.Try_Delay = function() {
// return true;
if( this.send_msg_queue.length <= 0 ) {
return false;
}
// if (this.status != NetStatus.CONNECTED)
// {
// return false;
// }
if( this.ws == null ) {
return false;
}
if( this.ws.readyState === WebSocketClass.OPEN ) {
this.Try_Send( null );
return true;
}
//延迟发送
// var self = this;
// self.Try_Finish(self);
// setTimeout(function(){
// if (self.ws.readyState === WebSocketClass.OPEN)
// {
// self.Try_Send(null);
// }
// return true;
// },30);
return true;
}
SocketConnect.prototype.Try_Finish = function( self ) {
if( self.send_msg_queue.length <= 0 ) return false;
if( self.ws.readyState == WebSocketClass.OPEN ) {
self.Try_Send( null );
return true;
} else {
setTimeout( () => {
this.Try_Finish( self );
}, 50 );
}
}
/*
@interface: SocketService
@desc: for Socket服务
*/
var SocketService = {};
// module.exports 适配js 和 nodejs
if( typeof ( module ) != "undefined" ) {
SocketService = module.exports;
}
/*
@method: InitWebSocket(websocket_class=null)
@desc: 初始化websocket,兼容chromefirefox,cocos
@param: [object]websocket_class 句柄
@return: [boolean]
*/
SocketService.InitWebSocket = function( websocket_class ) {
//导入第三方websocket
if( websocket_class != null ) {
WebSocketClass = websocket_class;
}
else {
//自动适配浏览器websocket(chrome,firefox)
if( typeof ( window ) != "undefined" ) {
if( typeof ( window.WebSocket ) == "undefined" ) {
WebSocketClass = window.MozWebSocket;
}
else {
WebSocketClass = window.WebSocket;
}
}
}
};
/*
@method: CreateConnect(is_ssl=false)
@desc: 创建连接
@param: [boolean]is_ssl 是否为ssl
@return: [boolean]
*/
SocketService.CreateConnect = function( is_ssl ) {
let p_connect = new SocketConnect( !!is_ssl );
if( p_connect ) {
this_connect_pool.push( p_connect );
}
return p_connect;
};
/*
@method: Release()
@desc: 系统退出,关闭所有连接
@return: [boolean]
*/
SocketService.Release = function() {
let p_connect = null;
for( var i = 0; i < this_connect_pool.length; i++ ) {
p_connect = this_connect_pool[ i ];
p_connect.Release();
}
this_connect_pool = [];
return true;
};
/*
@method: ErrCode()
@desc: 返回错误码定义
@return: [object]
*/
SocketService.ErrCode = function() {
return ErrorCode;
};