669 lines
15 KiB
JavaScript
669 lines
15 KiB
JavaScript
/**
|
||
* @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 心跳时间,单位ms,0表示不启用
|
||
@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,兼容chrome,firefox,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;
|
||
};
|