"use strict"; cc._RF.push(module, '2ae25g525BKXop4TohwM707', 'SocketService'); // Scripts/client/socket/SocketService.js "use strict"; /** * @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 var ErrorCode = { SUCC: 0, // 成功 FAIL: 1, // 失败 ERR_ALREADY_CONNECTED: 2, // 已经连接 FAIL_NOT_SUPPORT: 3 // 不支持 }; // net status(ws) var 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; } var 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) { var _this = this; 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(function () { var 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) { var _this2 = this; if (self.send_msg_queue.length <= 0) return false; if (self.ws.readyState == WebSocketClass.OPEN) { self.Try_Send(null); return true; } else { setTimeout(function () { _this2.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) { var 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 () { var 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; }; cc._RF.pop();