Cocos Creator Universal frame design - Network

Http request to initiate a Creator is relatively simple, but a lot of games and want to be able to maintain a long connection between the server so that the server can take the initiative to push messages to the client, and not always initiated by a client request, for real-time requirements high game even more so. Here we will design a generic network framework that can be easily applied to our project.

Use websocket

Before you implement this network framework, we first look at websocket, websocket tcp is based on full-duplex network protocol that allows web pages to create a persistent connection, two-way communication. Use websocket in Cocos Creator can be used in either the h5 web games, also supports native platforms Android and iOS.

Websocket construction objects

Websocket In use, the first step should create a websocket object constructor websocket object may pass two parameters, a first string is url, or the second protocol string is the string array, specifies acceptable the sub-protocol server need to choose one of the return, will establish a connection, but we generally use less.

url parameter is very important, it is divided into four parts 协议://地址:端口/资源, such as ws://echo.websocket.org:

  • Protocol: Required, default is ws agreement, if need secure encryption is used wss.
  • Address: Required, can be ip or domain name, of course, recommend the use of the domain name.
  • Port: Optional, in the case of not specified, the default port ws 80, the default port wss 443.
  • Resources: optional, generally followed a resource path after the domain name, we basically do not need it.

websocket state

websocket there are four states can be queried by the readyState property:

  • 0 CONNECTING connection has not been established.
  • 1 OPEN WebSocket connection is established, it may communicate.
  • 2 CLOSING ongoing connection handshake closed, or the close () method has been called.
  • 3 CLOSED connection is closed.

的 WebSocket API

websocket only two API, void send (data) and transmission data void close (code, reason) closes the connection.

receiving a send method parameter only - i.e., data to be transmitted, the type may be any of the following four types string | ArrayBufferLike | Blob | ArrayBufferView.

If the data to be transmitted is binary, we can specify the type of binary object by binaryType property websocket, binaryType can only be set to "blob" or "arraybuffer", the default is "blob". If we want to transfer this file is relatively fixed, for writing data to the disk, using the blob. And you want to transfer the objects are processed in memory using a more flexible arraybuffer. If the blob and other non-configuration data from a blob, the Blob constructor required.

There are two official recommended when sending data:

  • readyState object detection websocket whether OPEN, is only conducted send.
  • bufferedAmount websocket detection target is 0, is performed only send (to avoid accumulation of message, send the call attribute a rear accumulated in the buffer websocket not really sent data length).

close method of receiving two optional parameters, code indicates an error code, we should pass or an integer between 1 000 3000 ~ 4999, reason may be used to cause indicates a closed, can not exceed the length of 123 bytes.

websocket callback

websocket provides four callback binding for us:

  • onopen: After a successful call connection.
  • onmessage: when the news came to call: Incoming data object has properties may be strings, blob or arraybuffer.
  • Called when a network error:: onerror attribute data object has passed, usually a string describing the error.
  • Called when the connection is closed:: onclose incoming objects have code, reason, wasClean other attributes.

Note: When a network error, it will first call the onerror then call onclose, no matter what the cause of the connection is closed, onclose will be called.

Echo examples

The following websocket echo demo the official website of the code can be written to a html file and open the browser automatically creates websocket connection open, sends a message "WebSocket rocks" When connected, the server the message return, triggering onMessage, print information to the screen, and then close the connection. Specific reference http://www.websocket.org/echo.html.

The default url prefix is ​​wss, wss due ventilation, use ws only be connected, if ws also ventilation, you can even try this address ws: //121.40.165.18: 8800, which is the test of a free websocket URL.

  <!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>

reference

  • 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/

Design Framework

A generic network framework, under the premise of universal differences also need to be able to support the needs of various projects, based on experience, common requirement differences are as follows:

  • User Agreement difference, the game may transfer json, protobuf, flatbuffer or custom binary protocol
  • The underlying protocol differences, we may use websocket, or wx.websocket micro-channel mini-games, and even in native platform we want to use tcp / udp / kcp other agreements
  • Login authentication process, before long connection we should use to login authentication and login authentication of different games in different ways
  • Network exception handling, such as how long timeout, timeout after the performance is like, whether UI should be shielded wait for the server response request, show how the network disconnect automatically reconnect or by the player clicks reconnect button to reconnect, re even after whether to retransmit the message during off network? And so these.
  • Handle multiple connections, some games may need to support multiple different connections, generally not more than 2, such as a primary connection is responsible for processing hall and other business news, even fighting a battle directly connected servers, or connect to chat server.

According to the above requirements, we split functional module, the module try to ensure high cohesion and low coupling.

image

  • ProtocolHelper protocol processing module - when we get a buffer, we may need to know the buffer corresponding to the id of the agreement or how much, for example, when we requested was introduced to the process callback response, then the common practice may use an auto-incremented id to distinguish each request, or the protocol used to distinguish different numbers of request, which need to achieve a developer. We also need to obtain from the buffer in the packet length is how many? Reasonable packet size is how much? Heartbeat look like and so on.
  • Socket Module - to achieve the most basic communication functions are first defined ISocket Socket interface class, the definition of such connections, closed, transmission and reception of data interfaces, then subclass inherits and implement these interfaces.
  • NetworkTips network display module - implementing a state such as connection, reconnection, the load, the network disconnection a display, and ui shield.
  • NetNode network nodes - the so-called network node, in fact, the main responsibility is to the above functions together, to provide users with an easy to use interface.
  • Example NetManager single managed network node - we may have a plurality of network nodes (multiple connection), it is to be managed using a single embodiment, a single network node embodiment operates will be more convenient here.

ProtocolHelper

It is defined herein as a IProtocolHelper a simple interface, as follows:

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或协议类型
}

Socket

It is defined herein as a ISocket a simple interface, as follows:

// 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);  // 关闭接口
}

Next we achieve a WebSock, inherited from ISocket, we only need to implement connect, send and close to the interface. send and close to websocket are simple package, connect the need to create a url websocket constructed according to the parameters passed in ip, port, etc., and bind the callback 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 interface provides a very heavy and even request a switch, the framework will call them at the right time, we can inherit INetworkTips and customize our network-related message, to note that these interfaces may be called multiple times .

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

NetNode

NetNode is the most critical part of the whole network framework, a NetNode instance represents a complete connection object, based on NetNode we can easily expand, its main responsibilities are:

  • Connections Maintenance
    • Establish a connection with the authentication (authentication whether and how the authentication callback determined by the user)
    • Data retransmission processing reconnection
    • Heartbeat mechanism to ensure that the connection is valid (the heartbeat packet interval is defined by the contents of the heartbeat packet configured by ProtocolHelper)
    • The close connection
  • Data transmission
    • Support break retransmission timeout retransmission
    • The only support sending (to avoid sending duplicate the same time)
  • Data reception
    • Support continued to monitor
    • Support request-respone mode
  • Interface display
    • Customizable network delay performance, state and other short reconnection

The following is a complete code of 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 for managing NetNode, this is because we may need to support a number of different connection objects, you need to manage a dedicated NetManager NetNode, at the same time, NetManager as a singleton, it can also help us call the network.

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);
        }
    }

Test case

Next, we use a simple example to demonstrate the use of the network at the basic framework, first we need to fight a simple interface for display, three buttons (connection, transmission, closed), the two input frames (input url, enter the content transmission), a text box (the display data received from the server), as shown below.

The connection example is websocket official echo.websocket.org address, our server will send messages to it all as it is returned to us.

image

Next, implement a simple Component, here created a new NetExample.ts file, is very simple to do, create NetNode at initialization time, the default binding receive callbacks, will be returned to receive a callback server to display text in msgLabel . Next is connected, transmitting and closing implement several interfaces:

// 不关键的代码省略

@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();
    }
}

After the completion of the code, which is mounted to the scene Canvas node (the other node may be), then the scene RichText Label and drag properties panel of our NetExample:

image

Operating results are as follows:

image

summary

You can see, Websocket is easy to use, we will encounter a variety of needs and problems in the development process, to achieve a good design, and quickly solve the problem.

On the one hand we need to use our own technology in-depth understanding of how the underlying transport protocol websocket is achieved? And tcp, http where the difference in? Can you use udp based websocket transmit it? Websocket whether subcontracting is required to send data to their own data stream (websocket protocol to ensure a complete package)? Sending data if there is a buildup send buffer (see bufferedAmount)?

Also need to understand our own needs and usage scenarios, the more thorough understanding of the needs of the more able to make good design. What the needs related to the project, which needs are universal? General demand is required or optional? Different changes we should be encapsulated into a class or interface, multi-state approach to achieve it? Or provide configuration? Callback bindings? Event notification?

We need to design a good framework to apply to the next project, and iterative optimization in a one project, so as to build strong precipitation and improve efficiency.

Some experience before the next period of time will be organized into an open source framework to use cocos creator: https: //github.com/wyb10a10/cocos_creator_framework

Guess you like

Origin www.cnblogs.com/ybgame/p/11688944.html