Files
oops-plugin-framework/assets/libs/network/NetNode.ts

491 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { error, warn } from "cc";
import { Logger } from "../../core/common/log/Logger";
import { CallbackObject, INetworkTips, IProtocolHelper, IRequestProtocol, ISocket, NetCallFunc, NetData, RequestObject } from "./NetInterface";
/*
* CocosCreator网络节点基类以及网络相关接口定义
* 1. 网络连接、断开、请求发送、数据接收等基础功能
* 2. 心跳机制
* 3. 断线重连 + 请求重发
* 4. 调用网络屏蔽层
*/
type ExecuterFunc = (callback: CallbackObject, buffer: NetData) => void;
type CheckFunc = (checkedFunc: VoidFunc) => void;
type VoidFunc = () => void;
type BoolFunc = () => boolean;
var NetNodeStateStrs = ["已关闭", "连接中", "验证中", "可传输数据"];
/** 网络提示类型枚举 */
export enum NetTipsType {
Connecting,
ReConnecting,
Requesting,
}
/** 网络状态枚举 */
export enum NetNodeState {
Closed, // 已关闭
Connecting, // 连接中
Checking, // 验证中
Working, // 可传输数据
}
/** 网络连接参数 */
export interface NetConnectOptions {
host?: string, // 地址
port?: number, // 端口
url?: string, // url与地址+端口二选一
autoReconnect?: number, // -1 永久重连0不自动重连其他正整数为自动重试次数
}
/** 网络节点 */
export class NetNode {
protected _connectOptions: NetConnectOptions | null = null;
protected _autoReconnect: number = 0;
protected _isSocketInit: boolean = false; // Socket是否初始化过
protected _isSocketOpen: boolean = false; // Socket是否连接成功过
protected _state: NetNodeState = NetNodeState.Closed; // 节点当前状态
protected _socket: ISocket | null = null; // Socket对象可能是原生socket、websocket、wx.socket...)
protected _networkTips: INetworkTips | null = null; // 网络提示ui对象请求提示、断线重连提示等
protected _protocolHelper: IProtocolHelper | null = null; // 包解析对象
protected _connectedCallback: CheckFunc | null = null; // 连接完成回调
protected _disconnectCallback: BoolFunc | null = null; // 断线回调
protected _callbackExecuter: ExecuterFunc | null = null; // 回调执行
protected _keepAliveTimer: any = null; // 心跳定时器
protected _receiveMsgTimer: any = null; // 接收数据定时器
protected _reconnectTimer: any = null; // 重连定时器
protected _heartTime: number = 10000; // 心跳间隔
protected _receiveTime: number = 6000000; // 多久没收到数据断开
protected _reconnetTimeOut: number = 8000000; // 重连间隔
protected _requests: RequestObject[] = Array<RequestObject>(); // 请求列表
protected _listener: { [key: string]: CallbackObject[] | null } = {} // 监听者列表
/********************** 网络相关处理 *********************/
init(socket: ISocket, protocol: IProtocolHelper, networkTips: INetworkTips | null = null, execFunc: ExecuterFunc | null = null) {
Logger.instance.logNet(`网络初始化`);
this._socket = socket;
this._protocolHelper = protocol;
this._networkTips = networkTips;
this._callbackExecuter = execFunc ? execFunc : (callback: CallbackObject, buffer: NetData) => {
callback.callback.call(callback.target, buffer);
}
}
/**
* 请求连接服务器
* @param options 连接参数
*/
connect(options: NetConnectOptions): boolean {
if (this._socket && this._state == NetNodeState.Closed) {
if (!this._isSocketInit) {
this.initSocket();
}
this._state = NetNodeState.Connecting;
if (!this._socket.connect(options)) {
this.updateNetTips(NetTipsType.Connecting, false);
return false;
}
if (this._connectOptions == null && typeof options.autoReconnect == "number") {
this._autoReconnect = options.autoReconnect;
}
this._connectOptions = options;
this.updateNetTips(NetTipsType.Connecting, true);
return true;
}
return false;
}
protected initSocket() {
if (this._socket) {
this._socket.onConnected = (event) => { this.onConnected(event) };
this._socket.onMessage = (msg) => { this.onMessage(msg) };
this._socket.onError = (event) => { this.onError(event) };
this._socket.onClosed = (event) => { this.onClosed(event) };
this._isSocketInit = true;
}
}
protected updateNetTips(tipsType: NetTipsType, isShow: boolean) {
if (this._networkTips) {
if (tipsType == NetTipsType.Requesting) {
this._networkTips.requestTips(isShow);
}
else if (tipsType == NetTipsType.Connecting) {
this._networkTips.connectTips(isShow);
}
else if (tipsType == NetTipsType.ReConnecting) {
this._networkTips.reconnectTips(isShow);
}
}
}
/** 网络连接成功 */
protected onConnected(event: any) {
Logger.instance.logNet("网络已连接")
this._isSocketOpen = true;
// 如果设置了鉴权回调,在连接完成后进入鉴权阶段,等待鉴权结束
if (this._connectedCallback !== null) {
this._state = NetNodeState.Checking;
this._connectedCallback(() => { this.onChecked() });
}
else {
this.onChecked();
}
Logger.instance.logNet(`网络已连接当前状态为【${NetNodeStateStrs[this._state]}`);
}
/** 连接验证成功,进入工作状态 */
protected onChecked() {
Logger.instance.logNet("连接验证成功,进入工作状态");
this._state = NetNodeState.Working;
// 关闭连接或重连中的状态显示
this.updateNetTips(NetTipsType.Connecting, false);
this.updateNetTips(NetTipsType.ReConnecting, false);
// 重发待发送信息
var requests = this._requests.concat();
if (requests.length > 0) {
Logger.instance.logNet(`请求【${this._requests.length}】个待发送的信息`);
for (var i = 0; i < requests.length;) {
let req = requests[i];
this._socket!.send(req.buffer);
if (req.rspObject == null || req.rspCmd != "") {
requests.splice(i, 1);
}
else {
++i;
}
}
// 如果还有等待返回的请求,启动网络请求层
this.updateNetTips(NetTipsType.Requesting, this._requests.length > 0);
}
}
/** 接收到一个完整的消息包 */
protected onMessage(msg: any): void {
// Logger.logNet(`接受消息状态为【${NetNodeStateStrs[this._state]}】`);
var json = JSON.parse(msg);
// 进行头部的校验(实际包长与头部长度是否匹配)
if (!this._protocolHelper!.checkResponsePackage(json)) {
error(`校验接受消息数据异常`);
return;
}
// 处理相应包数据
if (!this._protocolHelper!.handlerResponsePackage(json)) {
if (this._networkTips)
this._networkTips.responseErrorCode(json.code);
}
// 接受到数据,重新定时收数据计时器
this.resetReceiveMsgTimer();
// 重置心跳包发送器
this.resetHearbeatTimer();
// 触发消息执行
let rspCmd = this._protocolHelper!.getPackageId(json);
Logger.instance.logNet(`接受到命令【${rspCmd}】的消息`);
// 优先触发request队列
if (this._requests.length > 0) {
for (let reqIdx in this._requests) {
let req = this._requests[reqIdx];
if (req.rspCmd == rspCmd && req.rspObject) {
Logger.instance.logNet(`触发请求命令【${rspCmd}】的回调`);
this._callbackExecuter!(req.rspObject, json.data);
this._requests.splice(parseInt(reqIdx), 1);
break;
}
}
if (this._requests.length == 0) {
this.updateNetTips(NetTipsType.Requesting, false);
}
else {
Logger.instance.logNet(`请求队列中还有【${this._requests.length}】个请求在等待`);
}
}
let listeners = this._listener[rspCmd];
if (null != listeners) {
for (const rsp of listeners) {
Logger.instance.logNet(`触发监听命令【${rspCmd}】的回调`);
this._callbackExecuter!(rsp, json.data);
}
}
}
protected onError(event: any) {
error(event);
}
protected onClosed(event: any) {
this.clearTimer();
// 执行断线回调返回false表示不进行重连
if (this._disconnectCallback && !this._disconnectCallback()) {
Logger.instance.logNet(`断开连接`);
return;
}
// 自动重连
if (this.isAutoReconnect()) {
this.updateNetTips(NetTipsType.ReConnecting, true);
this._reconnectTimer = setTimeout(() => {
this._socket!.close();
this._state = NetNodeState.Closed;
this.connect(this._connectOptions!);
if (this._autoReconnect > 0) {
this._autoReconnect -= 1;
}
}, this._reconnetTimeOut);
}
else {
this._state = NetNodeState.Closed;
}
}
/**
* 断开网络
* @param code 关闭码
* @param reason 关闭原因
*/
close(code?: number, reason?: string) {
this.clearTimer();
this._listener = {};
this._requests.length = 0;
if (this._networkTips) {
this._networkTips.connectTips(false);
this._networkTips.reconnectTips(false);
this._networkTips.requestTips(false);
}
if (this._socket) {
this._socket.close(code, reason);
}
else {
this._state = NetNodeState.Closed;
}
}
/**
* 只是关闭Socket套接字仍然重用缓存与当前状态
* @param code 关闭码
* @param reason 关闭原因
*/
closeSocket(code?: number, reason?: string) {
if (this._socket) {
this._socket.close(code, reason);
}
}
/**
* 发起请求,如果当前处于重连中,进入缓存列表等待重连完成后发送
* @param buf 网络数据
* @param force 是否强制发送
*/
send(buf: NetData, force: boolean = false): number {
if (this._state == NetNodeState.Working || force) {
return this._socket!.send(buf);
}
else if (this._state == NetNodeState.Checking ||
this._state == NetNodeState.Connecting) {
this._requests.push({
buffer: buf,
rspCmd: "",
rspObject: null
});
Logger.instance.logNet(`当前状态为【${NetNodeStateStrs[this._state]}】,繁忙并缓冲发送数据`);
return 0;
}
else {
error(`当前状态为【${NetNodeStateStrs[this._state]}】,请求错误`);
return -1;
}
}
/**
* 发起请求,并进入缓存列表
* @param reqProtocol 请求协议
* @param rspObject 回调对象
* @param showTips 是否触发请求提示
* @param force 是否强制发送
*/
request<T>(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) {
var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol);
this.base_request(reqProtocol, rspCmd, rspObject, showTips, force);
}
/**
* 唯一request确保没有同一响应的请求避免一个请求重复发送netTips界面的屏蔽也是一个好的方法
* @param reqProtocol 请求协议
* @param rspObject 回调对象
* @param showTips 是否触发请求提示
* @param force 是否强制发送
*/
requestUnique(reqProtocol: IRequestProtocol, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false): boolean {
var rspCmd = this._protocolHelper!.handlerRequestPackage(reqProtocol);
for (let i = 0; i < this._requests.length; ++i) {
if (this._requests[i].rspCmd == rspCmd) {
Logger.instance.logNet(`命令【${rspCmd}】重复请求`);
return false;
}
}
this.base_request(reqProtocol, rspCmd, rspObject, showTips, force);
return true;
}
private base_request(reqProtocol: IRequestProtocol, rspCmd: string, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) {
var buf: NetData = JSON.stringify(reqProtocol); // 转为二进制流发送
if (this._state == NetNodeState.Working || force) {
this._socket!.send(buf);
}
Logger.instance.logNet(`队列命令为【${rspCmd}】的请求,等待请求数据的回调`);
// 进入发送缓存列表
this._requests.push({
buffer: buf, rspCmd, rspObject
});
// 启动网络请求层
if (showTips) {
this.updateNetTips(NetTipsType.Requesting, true);
}
}
/********************** 回调相关处理 *********************/
/**
* 设置一个唯一的服务器推送监听
* @param cmd 命令字串
* @param callback 回调方法
* @param target 目标对象
*/
setResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean {
if (callback == null) {
error(`命令为【${cmd}】设置响应处理程序错误`);
return false;
}
this._listener[cmd] = [{ target, callback }];
return true;
}
/**
* 可添加多个同类返回消息的监听
* @param cmd 命令字串
* @param callback 回调方法
* @param target 目标对象
* @returns
*/
addResponeHandler(cmd: string, callback: NetCallFunc, target?: any): boolean {
if (callback == null) {
error(`命令为【${cmd}】添加响应处理程序错误`);
return false;
}
let rspObject = { target, callback };
if (null == this._listener[cmd]) {
this._listener[cmd] = [rspObject];
}
else {
let index = this.getNetListenersIndex(cmd, rspObject);
if (-1 == index) {
this._listener[cmd]!.push(rspObject);
}
}
return true;
}
/**
* 删除一个监听中指定子回调
* @param cmd 命令字串
* @param callback 回调方法
* @param target 目标对象
*/
removeResponeHandler(cmd: string, callback: NetCallFunc, target?: any) {
if (null != this._listener[cmd] && callback != null) {
let index = this.getNetListenersIndex(cmd, { target, callback });
if (-1 != index) {
this._listener[cmd]!.splice(index, 1);
}
}
}
/**
* 清除所有监听或指定命令的监听
* @param cmd 命令字串(默认不填为清除所有)
*/
cleanListeners(cmd: string = "") {
if (cmd == "") {
this._listener = {}
}
else {
delete this._listener[cmd];
}
}
protected getNetListenersIndex(cmd: string, rspObject: CallbackObject): number {
let index = -1;
for (let i = 0; i < this._listener[cmd]!.length; i++) {
let iterator = this._listener[cmd]![i];
if (iterator.callback == rspObject.callback
&& iterator.target == rspObject.target) {
index = i;
break;
}
}
return index;
}
/********************** 心跳、超时相关处理 *********************/
protected resetReceiveMsgTimer() {
if (this._receiveMsgTimer !== null) {
clearTimeout(this._receiveMsgTimer);
}
this._receiveMsgTimer = setTimeout(() => {
warn("接收消息定时器关闭网络连接");
this._socket!.close();
}, this._receiveTime);
}
protected resetHearbeatTimer() {
if (this._keepAliveTimer !== null) {
clearTimeout(this._keepAliveTimer);
}
this._keepAliveTimer = setTimeout(() => {
Logger.instance.logNet("网络节点保持活跃发送心跳信息");
this.send(this._protocolHelper!.getHearbeat());
}, this._heartTime);
}
protected clearTimer() {
if (this._receiveMsgTimer !== null) {
clearTimeout(this._receiveMsgTimer);
}
if (this._keepAliveTimer !== null) {
clearTimeout(this._keepAliveTimer);
}
if (this._reconnectTimer !== null) {
clearTimeout(this._reconnectTimer);
}
}
/** 是否自动重连接 */
isAutoReconnect() {
return this._autoReconnect != 0;
}
/** 拒绝重新连接 */
rejectReconnect() {
this._autoReconnect = 0;
this.clearTimer();
}
}