ココスクリエーターユニバーサルフレームデザイン - ネットワーク

Httpクリエーターを開始する要求は比較的簡単ですが、ゲームの多くと、サーバがクライアントにメッセージをプッシュするためのイニシアチブをとることができるように、サーバーとの間の長い接続を維持できるようにしたい、と常にリアルタイム要件のために、クライアントの要求によって開始されていませんなおさら高いゲーム。ここでは、簡単に私たちのプロジェクトに適用することができ、一般的なネットワークのフレームワークを設計します。

WebSocketをを使用してください

あなたはこのネットワークフレームワークを実装する前に、我々は最初のWebSocketを見て、のWebSocket TCPは、Webページが永続的な接続、双方向通信を作成することができ、全二重ネットワークプロトコルに基づいています。ココスクリエーターでのWebSocketはH5ウェブゲームのいずれかで使用することができます使用し、また、AndroidとiOSのネイティブプラットフォームをサポートしています。

WebSocketを建設オブジェクト

使用に際してのWebSocketは、のWebSocketオブジェクトコンストラクタ用WebSocketオブジェクトを作成する必要が最初のステップは、許容される指定、最初の文字列がURLであるか、または第2のプロトコル文字列が文字列配列され、2つのパラメータを渡すことができますサブプロトコル・サーバーは、接続を確立し、リターンのいずれかを選択する必要がありますが、我々は一般的にあまりを使用しています。

URLパラメータが非常に重要であり、それは、4つの部分に分かれている协议://地址:端口/资源など、ws://echo.websocket.org

  • プロトコル:セキュアな暗号化がWSSを使用されている必要がある場合は必須、デフォルトでは、WS契約です。
  • 住所:必須では、ドメイン名の使用をお勧めします、もちろん、IPまたはドメイン名を指定できます。
  • ポート:オプション、指定されない場合には、80 WSデフォルトのポート、443 WSSデフォルトポート。
  • リソース:オプションは、一般的に、我々は基本的にそれを必要としない、ドメイン名の後にリソースパスを追いました。

WebSocketの状態

readyStateのプロパティで照会することができる4つの状態がありWebSocketのとおりです。

  • 0接続する接続が確立されていません。
  • 1つのOPENのWebSocket接続が確立され、それが通信することができます。
  • 2 CLOSING進行中の接続ハンドシェイクが閉じられ、またはclose()メソッドが呼び出されています。
  • 3 CLOSED接続が閉じられています。

的のWebSocket API

WebSocketの2つだけAPI、無効送信(データ)および送信データボイド近い(コード、理由は)接続を閉じます。

のみ送信メソッドのパラメータを受信する-すなわち、データが送信されるように、タイプは、次の4つのタイプのいずれであってもよいですstring | ArrayBufferLike | Blob | ArrayBufferView

データを送信する場合はバイナリで、我々はbinaryTypeプロパティ用WebSocketでバイナリオブジェクトの種類を指定することができ、binaryTypeのみ「ブロブ」または「arraybuffer」に設定することができ、デフォルトでは「ブロブ」です。私たちは、このファイルを転送したい場合は、比較的ブロブを使用して、データをディスクに書き込むため、固定されています。そして、あなたはより柔軟arraybufferを使用してオブジェクトをメモリ内で処理されて転送します。BLOBからBLOBおよび他の非構成データ場合、ブロブコンストラクタが必要。

データを送信するときに、2つの公式推奨があります。

  • OPENかどうか、だけで行われる送信のWebSocket readyStateの物体検出。
  • bufferedAmountのWebSocket検出対象は、(実際にはデータ長を送信していないバッファ用WebSocketに蓄積されたリア属性コールを送って、メッセージの蓄積を避けるために)送るだけ実行され、0です。

2つのオプションのパラメータを受信する近隣方法は、コードが閉じたことを示すエラーコード、我々が通過する必要があり、または、理由を引き起こすために使用することができる〜4999 1 000 3000の整数を示し、123バイトの長さを超えることはできません。

WebSocketのコールバック

WebSocketのは、私たちのためにバインディング4つのコールバックを提供します。

  • 開く時]:成功した呼接続した後。
  • onMessage:ニュースは呼び出すようになりました:受信データオブジェクトは、プロパティは、文字列、BLOBまたはarraybufferかもしれ持っています。
  • ネットワークエラー:: ONERROR属性データオブジェクトは、エラーを説明する通常の文字列を通過したときに呼び出されます。
  • 接続が閉じているときに、着信::オブジェクトコード、理由、wasClean他の属性を持っていOnCloseの呼び出されます。

注意:ときにネットワークエラー、それは最初に関係なく、接続の原因が閉じられているものを、OnCloseのは呼ばれません、OnCloseの呼び出し後、ONERRORを呼び出します。

エコーの例

エコーは、コードの公式ウェブサイトは、htmlファイルに書き込まれ、ブラウザを開くことができますデモ次のWebSocketは自動的に、オープンのWebSocket接続を作成する接続した場合のメッセージ「のWebSocket岩」を送信し、サーバーのメッセージ画面へのonMessage、印刷情報をトリガし、返し、次に接続を閉じます。特定の参照http://www.websocket.org/echo.html。

//121.40.165.18:8800、無料のWebSocket URLのテストでデフォルトのURLプレフィックスもWSは換気場合、あなたもこのアドレスWSを試すことができ、WSS、WSSによる換気、のみ接続することが使用WSです。

  <!DOCTYPE html>
  <meta charset="utf-8" />
  <title>WebSocket Test</title>
  <script language="javascript" type="text/javascript">

  var wsUri = "ws://echo.websocket.org/";
  var output;

  function init() {
    output = document.getElementById("output");
    testWebSocket();
  }

  function testWebSocket() {
    // 初始化websocket,绑定回调
    websocket = new WebSocket(wsUri);
    websocket.onopen = onOpen;
    websocket.onclose = onClose;
    websocket.onmessage = onMessage;
    websocket.onerror = onError;
  }

  function onOpen(evt) {
    writeToScreen("CONNECTED");
    doSend("WebSocket rocks");
  }

  function onClose(evt) {
    writeToScreen("DISCONNECTED");
  }

  function onMessage(evt) {
    writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
    websocket.close();
  }

  function onError(evt) {
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
  }

  function doSend(message) {
    writeToScreen("SENT: " + message);
    websocket.send(message);
  }

  function writeToScreen(message) {
    var pre = document.createElement("p");
    pre.style.wordWrap = "break-word";
    pre.innerHTML = message;
    output.appendChild(pre);
  }

  // 加载时调用init方法,初始化websocket
  window.addEventListener("load", init, false);
  </script>
  
  <h2>WebSocket Test</h2>
  <div id="output"></div>

参照

  • https://www.w3.org/TR/websockets/
  • https://developer.mozilla.org/en-US/docs/Web/API/Blob
  • http://www.websocket.org/echo.html
  • http://www.websocket-test.com/

設計フレームワーク

次のように一般的なネットワークのフレームワークは、また、経験に基づいて、様々なプロジェクトのニーズに対応できるようにする必要がユニバーサル違いの前提の下で、一般的な要件の相違点は次のとおりです。

  • ユーザー契約の違い、ゲームは、JSON、いるProtobuf、flatbufferまたはカスタムバイナリプロトコルを転送することができます。
  • 基本的なプロトコルの違いは、我々はWebSocketを、またはwx.websocketマイクロチャネルのミニゲームを使用することができ、さらにはネイティブプラットフォームでは、我々はTCP / UDP / KCPその他の契約を使用したいです
  • ログイン認証プロセス、長い接続する前に、私たちはさまざまな方法で別のゲームの認証およびログイン認証にログインするために使用する必要があります
  • そのようなUIがサーバーの応答要求待ちをシールドする必要があるかどうか、パフォーマンス後のタイムアウトは、似ているどのくらいのタイムアウトなどのネットワーク例外処理、ネットワークの切断が自動的に再接続やクリックボタンが再接続するために再接続プレイヤー、再でどのように表示されでもオフネットワーク中にメッセージを再送信するかどうかの後に?だからこれら。
  • 複数の接続を扱う、いくつかのゲームでも、直接サーバーに接続されている戦いを戦って、処理場や他のビジネスニュースを担当して、このようなプライマリ接続として、2以上ではない一般的に、複数の異なる接続をサポートする必要がある、またはサーバをチャットに接続することがあります。

上記の要件によると、私たちは、機能モジュールを分割し、モジュールは、高凝集や低カップリングを確保しようとします。

画像

  • ProtocolHelperプロトコル処理モジュール - 私たちは、バッファを得るとき、私たちは例えば、契約またはどのくらいのIDに対応するバッファを知っている必要があり、我々は要求されたプロセスのコールバック応答に導入し、その後、一般的な方法で使用することができます自動インクリメントIDは、各要求、または開発を達成するために必要とする要求の数が異なる、区別するために使用されるプロトコルを区別します。また、どのように多くのパケット長にバッファから取得する必要がありますか?合理的なパケットサイズはどのくらいですか?ハートビートは、その上のように見えると。
  • 最も基本的な通信機能が最初に、ISocketソケットデータインターフェースのインターフェースクラス、そのような接続の定義、閉じ、送受信定義次いでサブクラスが継承し、これらのインタフェースを実装している達成するために - ソケットモジュール。
  • NetworkTipsネットワークディスプレイモジュール - このような接続、再接続、負荷、ネットワーク切断ディスプレイ、およびUIシールドような状態を実現します。
  • NETNODEネットワークノード - いわゆるネットワークノード、実際には、主な責任は、使いやすいインタフェースをユーザに提供するために、一緒に上記の機能です。
  • 例えばNetManagerは、単一のネットワークノードを管理 - 私たちは複数のネットワークノード(複数接続)を有していてもよく、それは単一の実施形態を使用して管理される、単一のネットワークノードの実施形態は、ここではより便利であろう動作します。

ProtocolHelper

それは次のように、シンプルなインターフェースIProtocolHelperとして定義されます。

export type NetData = (string | ArrayBufferLike | Blob | ArrayBufferView);

// 协议辅助接口
export interface IProtocolHelper {
    getHeadlen(): number;                   // 返回包头长度
    getHearbeat(): NetData;                 // 返回一个心跳包
    getPackageLen(msg: NetData): number;    // 返回整个包的长度
    checkPackage(msg: NetData): boolean;    // 检查包数据是否合法
    getPackageId(msg: NetData): number;     // 返回包的id或协议类型
}

ソケット

それは次のように、シンプルなインターフェースISocketとして定義されます。

// Socket接口
export interface ISocket {
    onConnected: (event) => void;           // 连接回调
    onMessage: (msg: NetData) => void;      // 消息回调
    onError: (event) => void;               // 错误回调
    onClosed: (event) => void;              // 关闭回调
    
    connect(ip: string, port: number);      // 连接接口
    send(buffer: NetData);                  // 数据发送接口
    close(code?: number, reason?: string);  // 关闭接口
}

次に我々はISocketから継承し、WebSockを達成するため、私たちは、接続の送信およびインターフェースの近くに実装する必要があります。送信し、WebSocketの近くなど、IP、ポート、中に渡されたパラメータに基づいて構築されたURL用WebSocketを作成し、コールバック用WebSocketをバインドする必要性を接続し、シンプルなパッケージです。

export class WebSock implements ISocket {
    private _ws: WebSocket = null;              // websocket对象

    onConnected: (event) => void = null;
    onMessage: (msg) => void = null;
    onError: (event) => void = null;
    onClosed: (event) => void = null;

    connect(options: any) {
        if (this._ws) {
            if (this._ws.readyState === WebSocket.CONNECTING) {
                console.log("websocket connecting, wait for a moment...")
                return false;
            }
        }

        let url = null;
        if(options.url) {
            url = options.url;
        } else {
            let ip = options.ip;
            let port = options.port;
            let protocol = options.protocol;
            url = `${protocol}://${ip}:${port}`;    
        }

        this._ws = new WebSocket(url);
        this._ws.binaryType = options.binaryType ? options.binaryType : "arraybuffer";
        this._ws.onmessage = (event) => {
            this.onMessage(event.data);
        };
        this._ws.onopen = this.onConnected;
        this._ws.onerror = this.onError;
        this._ws.onclose = this.onClosed;
        return true;
    }

    send(buffer: NetData) {
        if (this._ws.readyState == WebSocket.OPEN)
        {
            this._ws.send(buffer);
            return true;
        }
        return false;
    }

    close(code?: number, reason?: string) {
        this._ws.close();
    }
}

NetworkTips

INetworkTipsインターフェイスは非常に重いを提供しても、スイッチを要求し、フレームワークは、私たちがINetworkTipsを継承し、当社のネットワーク関連のメッセージをカスタマイズし、これらのインターフェイスができることに留意することができ、適切なタイミングでそれらを呼び出す複数回呼ばれます

// 网络提示接口
export interface INetworkTips {
    connectTips(isShow: boolean): void;
    reconnectTips(isShow: boolean): void;
    requestTips(isShow: boolean): void;
}

NETNODE

NETNODEは、ネットワーク全体の枠組みの中で最も重要な部分である、NETNODEインスタンスは、我々は簡単に拡張することができますNETNODEに基づいて、完全な接続オブジェクトを表し、その主な責務は以下のとおりです。

  • 接続のメンテナンス
    • 認証(ユーザによって決定認証するかどうか、どのように認証用コールバック)との接続を確立
    • データの再送処理の再接続
    • 接続が有効であることを確認するために、ハートビート機構(ハートビートパケット間隔がProtocolHelperで構成ハートビートパケットの内容によって定義されます)
    • 近くに接続
  • データ送信
    • サポートブレーク再送タイムアウト再送信
    • (同じ時間を複製送信を回避するために)送信のみをサポート
  • データ受信
    • サポートが監視を続けて
    • サポート要求responeモード
  • インタフェースの表示
    • カスタマイズ可能なネットワーク遅延のパフォーマンス、状態及びその他の短期再接続

次はNETNODEの完全なコードです:

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;
    protected _autoReconnect: number = 0;
    protected _isSocketInit: boolean = false;                               // Socket是否初始化过
    protected _isSocketOpen: boolean = false;                               // Socket是否连接成功过
    protected _state: NetNodeState = NetNodeState.Closed;                   // 节点当前状态
    protected _socket: ISocket = null;                                      // Socket对象(可能是原生socket、websocket、wx.socket...)

    protected _networkTips: INetworkTips = null;                            // 网络提示ui对象(请求提示、断线重连提示等)
    protected _protocolHelper: IProtocolHelper = null;                      // 包解析对象
    protected _connectedCallback: CheckFunc = null;                         // 连接完成回调
    protected _disconnectCallback: BoolFunc = null;                         // 断线回调
    protected _callbackExecuter: ExecuterFunc = 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: number]: CallbackObject[] } = {}           // 监听者列表

    /********************** 网络相关处理 *********************/
    public init(socket: ISocket, protocol: IProtocolHelper, networkTips: any = null, execFunc : ExecuterFunc = null) {
        console.log(`NetNode init socket`);
        this._socket = socket;
        this._protocolHelper = protocol;
        this._networkTips = networkTips;
        this._callbackExecuter = execFunc ? execFunc : (callback: CallbackObject, buffer: NetData) => {
            callback.callback.call(callback.target, 0, buffer);
        }
    }

    public 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) {
                options.autoReconnect = options.autoReconnect;
            }
            this._connectOptions = options;
            this.updateNetTips(NetTipsType.Connecting, true);
            return true;
        }
        return false;
    }

    protected initSocket() {
        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) {
        console.log("NetNode onConnected!")
        this._isSocketOpen = true;
        // 如果设置了鉴权回调,在连接完成后进入鉴权阶段,等待鉴权结束
        if (this._connectedCallback !== null) {
            this._state = NetNodeState.Checking;
            this._connectedCallback(() => { this.onChecked() });
        } else {
            this.onChecked();
        }
        console.log("NetNode onConnected! state =" + this._state);
    }

    // 连接验证成功,进入工作状态
    protected onChecked() {
        console.log("NetNode onChecked!")
        this._state = NetNodeState.Working;
        // 关闭连接或重连中的状态显示
        this.updateNetTips(NetTipsType.Connecting, false);
        this.updateNetTips(NetTipsType.ReConnecting, false);

        // 重发待发送信息
        console.log(`NetNode flush ${this._requests.length} request`)
        if (this._requests.length > 0) {
            for (var i = 0; i < this._requests.length;) {
                let req = this._requests[i];
                this._socket.send(req.buffer);
                if (req.rspObject == null || req.rspCmd <= 0) {
                    this._requests.splice(i, 1);
                } else {
                    ++i;
                }
            }
            // 如果还有等待返回的请求,启动网络请求层
            this.updateNetTips(NetTipsType.Requesting, this.request.length > 0);
        }
    }

    // 接收到一个完整的消息包
    protected onMessage(msg): void {
        // console.log(`NetNode onMessage status = ` + this._state);
        // 进行头部的校验(实际包长与头部长度是否匹配)
        if (!this._protocolHelper.check P a c ka ge(msg)) {
            console.error(`NetNode checkHead Error`);
            return;
        }
        // 接受到数据,重新定时收数据计时器
        this.resetReceiveMsgTimer();
        // 重置心跳包发送器
        this.resetHearbeatTimer();
        // 触发消息执行
        let rspCmd = this._protocolHelper.getPackageId(msg);
        console.log(`NetNode onMessage rspCmd = ` + rspCmd);
        // 优先触发request队列
        if (this._requests.length > 0) {
            for (let reqIdx in this._requests) {
                let req = this._requests[reqIdx];
                if (req.rspCmd == rspCmd) {
                    console.log(`NetNode execute request rspcmd ${rspCmd}`);
                    this._callbackExecuter(req.rspObject, msg);
                    this._requests.splice(parseInt(reqIdx), 1);
                    break;
                }
            }
            console.log(`NetNode still has ${this._requests.length} request watting`);
            if (this._requests.length == 0) {
                this.updateNetTips(NetTipsType.Requesting, false);
            }
        }

        let listeners = this._listener[rspCmd];
        if (null != listeners) {
            for (const rsp of listeners) {
                console.log(`NetNode execute listener cmd ${rspCmd}`);
                this._callbackExecuter(rsp, msg);
            }
        }
    }

    protected onError(event) {
        console.error(event);
    }

    protected onClosed(event) {
        this.clearTimer();

        // 执行断线回调,返回false表示不进行重连
        if (this._disconnectCallback && !this._disconnectCallback()) {
            console.log(`disconnect return!`)
            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;
        }
    }

    public 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套接字(仍然重用缓存与当前状态)
    public closeSocket(code?: number, reason?: string) {
        if (this._socket) {
            this._socket.close(code, reason);
        }
    }

    // 发起请求,如果当前处于重连中,进入缓存列表等待重连完成后发送
    public send(buf: NetData, force: boolean = false): boolean {
        if (this._state == NetNodeState.Working || force) {
            console.log(`socket send ...`);
            return this._socket.send(buf);
        } else if (this._state == NetNodeState.Checking ||
            this._state == NetNodeState.Connecting) {
            this._requests.push({
                buffer: buf,
                rspCmd: 0,
                rspObject: null
            });
            console.log("NetNode socket is busy, push to send buffer, current state is " + this._state);
            return true;
        } else {
            console.error("NetNode request error! current state is " + this._state);
            return false;
        }
    }

    // 发起请求,并进入缓存列表
    public request(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) {
        if (this._state == NetNodeState.Working || force) {
            this._socket.send(buf);
        }
        console.log(`NetNode request with timeout for ${rspCmd}`);
        // 进入发送缓存列表
        this._requests.push({
            buffer: buf, rspCmd, rspObject
        });
        // 启动网络请求层
        if (showTips) {
            this.updateNetTips(NetTipsType.Requesting, true);
        }
    }

    // 唯一request,确保没有同一响应的请求(避免一个请求重复发送,netTips界面的屏蔽也是一个好的方法)
    public requestUnique(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false): boolean {
        for (let i = 0; i < this._requests.length; ++i) {
            if (this._requests[i].rspCmd == rspCmd) {
                console.log(`NetNode requestUnique faile for ${rspCmd}`);
                return false;
            }
        }
        this.request(buf, rspCmd, rspObject, showTips, force);
        return true;
    }

    /********************** 回调相关处理 *********************/
    public setResponeHandler(cmd: number, callback: NetCallFunc, target?: any): boolean {
        if (callback == null) {
            console.error(`NetNode setResponeHandler error ${cmd}`);
            return false;
        }
        this._listener[cmd] = [{ target, callback }];
        return true;
    }

    public addResponeHandler(cmd: number, callback: NetCallFunc, target?: any): boolean {
        if (callback == null) {
            console.error(`NetNode addResponeHandler 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;
    }

    public removeResponeHandler(cmd: number, 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);
            }
        }
    }

    public cleanListeners(cmd: number = -1) {
        if (cmd == -1) {
            this._listener = {}
        } else {
            this._listener[cmd] = null;
        }
    }

    protected getNetListenersIndex(cmd: number, 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(() => {
            console.warn("NetNode recvieMsgTimer close socket!");
            this._socket.close();
        }, this._receiveTime);
    }

    protected resetHearbeatTimer() {
        if (this._keepAliveTimer !== null) {
            clearTimeout(this._keepAliveTimer);
        }

        this._keepAliveTimer = setTimeout(() => {
            console.log("NetNode keepAliveTimer send Hearbeat")
            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);
        }
    }

    public isAutoReconnect() {
        return this._autoReconnect != 0;
    }

    public rejectReconnect() {
        this._autoReconnect = 0;
        this.clearTimer();
    }
}

NetManager

NetManagerはNETNODEを管理するために、これは我々が異なる接続オブジェクトの数をサポートする必要があるかもしれないので、あなたは同時に、NetManagerはシングルトンとして、それはまた、私たちはネットワークを呼び出すことができ、専用のNetManager NETNODEを管理する必要があります。

export class NetManager {
    private static _instance: NetManager = null;
    protected _channels: { [key: number]: NetNode } = {};

    public static getInstance(): NetManager {
        if (this._instance == null) {
            this._instance = new NetManager();
        }
        return this._instance;
    }

    // 添加Node,返回ChannelID
    public setNetNode(newNode: NetNode, channelId: number = 0) {
        this._channels[channelId] = newNode;
    }

    // 移除Node
    public removeNetNode(channelId: number) {
        delete this._channels[channelId];
    }

    // 调用Node连接
    public connect(options: NetConnectOptions, channelId: number = 0): boolean {
        if (this._channels[channelId]) {
            return this._channels[channelId].connect(options);
        }
        return false;
    }

    // 调用Node发送
    public send(buf: NetData, force: boolean = false, channelId: number = 0): boolean {
        let node = this._channels[channelId];
        if(node) {
            return node.send(buf, force);
        }
        return false;
    }

    // 发起请求,并在在结果返回时调用指定好的回调函数
    public request(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0) {
        let node = this._channels[channelId];
        if(node) {
            node.request(buf, rspCmd, rspObject, showTips, force);
        }
    }

    // 同request,但在request之前会先判断队列中是否已有rspCmd,如有重复的则直接返回
    public requestUnique(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: number = 0): boolean {
        let node = this._channels[channelId];
        if(node) {
            return node.requestUnique(buf, rspCmd, rspObject, showTips, force);
        }
        return false;
    }

    // 调用Node关闭
    public close(code?: number, reason?: string, channelId: number = 0) {
        if (this._channels[channelId]) {
            return this._channels[channelId].closeSocket(code, reason);
        }
    }

テストケース

次に、我々は三つのボタン(接続、伝送、閉じた状態)、まず私たちは、ディスプレイのためのシンプルなインターフェイスを戦うために必要な、基本的な枠組みでのネットワークの使用を示すために簡単な例を使用し、二つの入力フレーム(入力されたURLは、入力します。コンテンツ伝送)、以下に示すように、テキストボックス(サーバから受信した表示データ)。

接続例は、公式echo.websocket.orgアドレスのWebSocketであるそれは私たちに返されるよう、弊社のサーバーはそれをすべてにメッセージを送信します。

画像

次に、ここでは、簡単なコンポーネントを実装する新しいNetExample.tsファイルを作成し、やることは非常に簡単ですが、初期化時にNETNODEを作成し、コールバックを受信結合デフォルトでは、msgLabelでテキストを表示するためのコールバックサーバを受け取るために返されます。次送信及び複数のインターフェイスを実装する閉じ、接続されています。

// 不关键的代码省略

@ccclass
export default class NetExample extends cc.Component {
    @property(cc.Label)
    textLabel: cc.Label = null;
    @property(cc.Label)
    urlLabel: cc.Label = null;
    @property(cc.RichText)
    msgLabel: cc.RichText = null;
    private lineCount: number = 0;

    onLoad() {
        let Node = new NetNode();
        Node.init(new WebSock(), new DefStringProtocol());
        Node.setResponeHandler(0, (cmd: number, data: NetData) => {
            if (this.lineCount > 5) {
                let idx = this.msgLabel.string.search("\n");
                this.msgLabel.string = this.msgLabel.string.substr(idx + 1);
            }
            this.msgLabel.string += `${data}\n`;
            ++this.lineCount;
        });
        NetManager.getInstance().setNetNode(Node);
    }

    onConnectClick() {
        NetManager.getInstance().connect({ url: this.urlLabel.string });
    }

    onSendClick() {
        NetManager.getInstance().send(this.textLabel.string);
    }

    onDisconnectClick() {
        NetManager.getInstance().close();
    }
}

(他のノードであってもよい)シーンキャンバスノードに搭載されているコードの完了後、次に我々のNetExampleのシーンリッチテキストラベルとドラッグプロパティパネル:

画像

次のように業績は以下のとおりです。

画像

概要

あなたは、私たちは良いデザインを実現するために、開発プロセスのニーズやさまざまな問題が発生します、のWebSocketは使いやすいです、見て、問題を迅速に解決することができます。

一方で我々は、基礎となるトランスポートプロトコル用WebSocketが達成されるかの当社独自の技術の深い理解を使用する必要がありますか?そして、TCP、HTTPどこの違い?あなたは、UDPベースのWebSocketを使用することができ、それを送信しますか?かどうかのWebSocket委託は(完全なパッケージを確実にするためのWebSocketプロトコル)独自のデータストリームにデータを送信するために必要ですか?ビルドアップ送信バッファがある場合(bufferedAmountを参照)のデータを送信しますか?

また、私たち自身のニーズや利用シナリオ、良いデザインを作ることがもっとできるのニーズのより完全な理解を理解する必要があります。必要なものをプロジェクトに関連するニーズは、普遍的なもの?一般的な需要が必須かオプション?別の変更は、我々はそれを達成するために、クラスやインタフェース、マルチステートアプローチにカプセル化すべきか?または構成を提供?コールバックのバインディング?イベント通知?

私たちは、強い降水を構築し、効率を向上させるために、一つのプロジェクトの次のプロジェクトに適用するのは良いフレームワーク、および反復最適化を設計する必要があります。

時間の次の期間までにいくつかの経験がココスクリエーターを使用するために、オープンソースのフレームワークに編成されます。https://github.com/wyb10a10/cocos_creator_framework

おすすめ

転載: www.cnblogs.com/ybgame/p/11688944.html