/** * @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; };