网络通信

一、概述

在我们的项目开发过程中,除了单机不需要使用网络通信,开发一个网络项目,难免要处理网络通信。本章将讲解LayaAir的网络通信部分。通常我们使用 Http 和WebSocket 这两种网络通信方式。首先我们来对比一下两者的区别:

  • HTTP

优点:协议成熟,应用广泛,基于TCP/IP,拥有TCP的可靠性,研发成本低,开发快速,被广泛支持,如nginx/apache/tomcat等。

缺点:无状态无连接,只有PULL模式,不支持PUSH,数据报文较大,每次请求都要带上所有的头信息。

特性:无状态,无连接(短连接),支持C/S模式,适用于文本传输,但也可以传输其他类型的数据,如图片、视频等。

  • WebSocket

优点:协议较新,但已趋于成熟,基于TCP/IP,拥有TCP的可靠性,数据报文较小,只有在建立连接时的握手阶段需要较大的头信息,之后的数据交换阶段头信息较小,面向连接,有状态协议,支持服务器主动推送数据(PUSH模式)。

缺点:WebSocket 是应用层协议,虽然数据包较简洁,但相比于TCP/IP协议,其数据包头部信息相对较大,对于非常小的数据包,这可能会导致一些额外的流量消耗。此外,由于需要维持连接,可能会占用更多的服务器资源。

特性:有状态,面向连接,数据报头较小,支持全双工通信,适用于需要实时通信的应用。

通过以上对协议特性分析,建议:

1,对于弱联网类游戏,比如消除类的,卡牌类的,可以直接HTTP协议,考虑安全的话直接HTTPS,或者对内容体做对称加密;

2,对于实时性,交互性要求较高,且team有过相关经验,可以优先选择websocket协议,比如SLG和RPG等大型网络游戏。

二、Http连接

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

2.1 Laya.HttpRequest

在LayaAir引擎中 HttpRequest 就是我们发送请求的基本类。HttpRequest 类其实包装的就是原生的 XMLHttpRequest,我们先来了解下 HttpRequest

2.1.1 原生 XMLHttpRequest 对象

    /**
     * 本对象所封装的原生 XMLHttpRequest 引用。
     */
    get http(): any {
        return this._http;
    }

通过 ._http 属性可以获得XMLHttpRequestXMLHttpRequest 中文可以解释为可扩展超文本传输请求。它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。

1,属性

属性 类型 描述
onreadystatechange function 一个JavaScript函数对象,当readyState属性改变时会调用它。
readyState unsigned short 请求的五种状态
response varies 响应实体的类型由 responseType 来指定, 可以是 ArrayBufferBlobDocument, JavaScript 对象 (即 “json”), 或者是字符串。如果请求未完成或失败,则该值为 null
responseText DOMString 此次请求的响应为文本,或是当请求未成功或还未发送时为 null只读。
responseType XMLHttpRequestResponseType 设置该值能够改变响应类型。就是告诉服务器你期望的响应格式。
status unsigned short 该请求的响应状态码 (例如,状态码200 表示一个成功的请求).只读
statusText DOMString 该请求的响应状态信息,包含一个状态码和原因短语 (例如 “200 OK“)。 只读
upload XMLHttpRequestUpload 可以在 upload 上添加一个事件监听来跟踪上传过程。
withCredentials boolean 表明在进行跨站(cross-site)的访问控制(Access-Control)请求时,是否使用认证信息(例如cookie或授权的header)。 默认为 false
timeout number 请求超时时间

2,方法

abort()

如果请求已经被发送,则立刻中止请求。

getAllResponseHeaders()

返回所有响应头信息(响应头名和值), 如果响应头还没接受,则返回null

getResponseHeader()

返回指定的响应头的值, 如果响应头还没被接受,或该响应头不存在,则返回null。

open()

初始化一个请求.

send()

发送请求. 如果该请求是异步模式(默认),该方法会立刻返回。 相反,如果请求是同步模式,则直到请求的响应完全接受以后,该方法才会返回。

setRequestHeader()

给指定的HTTP请求头赋值。在这之前,你必须确认已经调用 open()方法打开了一个url。

因此使用 HttpRequest 的过程中,我们也可以获得 XMLHttpRequest 对象,并对 XMLHttpRequest 对象做相关的操作,在此我们就不对 XMLHttpRequest 做过多讲解,开发者可以自行查阅相关文档。

2.1.2 send() 方法

发送请求, 通常发送的请求是异步方式,其中send的参数类型如下:

    /**
     * 发送 HTTP 请求。
     * @param    url                请求的地址。大多数浏览器实施了一个同源安全策略,并且要求这个 URL 与包含脚本的文本具有相同的主机名和端口。
     * @param    data            (default = null)发送的数据。
     * @param    method            (default = "get")用于请求的 HTTP 方法。值包括 "get"、"post"、"head"。
     * @param    responseType    (default = "text")Web 服务器的响应类型,可设置为 "text"、"json"、"xml"、"arraybuffer"。
     * @param    headers            (default = null) HTTP 请求的头部信息。参数形如key-value数组:key是头部的名称,不应该包括空白、冒号或换行;value是头部的值,不应该包括换行。比如["Content-Type", "application/json"]。
     */
    send(url: string, data: any = null,
        method: "get" | "post" | "head" = "get",
        responseType: "text" | "json" | "xml" | "arraybuffer" = "text",
        headers: any[] | null = null)

2.1.3 支持的事件类型

我们常用的基本就是进度事件,完成事件,错误事件等

/**
 * 请求进度改变时调度。
 * @eventType Event.PROGRESS
 * */
/*[Event(name = "progress", type = "laya.events.Event")]*/
/**
 * 请求结束后调度。
 * @eventType Event.COMPLETE
 * */
/*[Event(name = "complete", type = "laya.events.Event")]*/
/**
 * 请求出错时调度。
 * @eventType Event.ERROR
 * */
/*[Event(name = "error", type = "laya.events.Event")]*/

2.1.4 在代码中怎么使用

LayaAir引擎中用 HttpRequest 继承的是 EventDispatcher,具有事件派发的功能。加上本身具备发送请求的功能。我们写个简单的例子来看下用法:

class LayaSample {
    constructor() {

        //创建HttpRequest对象
        let http: Laya.HttpRequest = new Laya.HttpRequest();
        //设置超时时间
        http.http.timeout = 10000;
        //发送了一个简单的请求
        http.send("resources/data.txt", "", "get", "text");//需要在resources文件夹下新建一个data.txt文件
        //设置完成事件,添加回调方法
        http.once(Laya.Event.COMPLETE, this, this.completeHandler);
        //设置错误事件,添加回调方法        
        http.once(Laya.Event.ERROR, this, this.errorHandler);
        //设置进度事件,添加回调方法        
        http.on(Laya.Event.PROGRESS, this, this.processHandler);
    }

    private processHandler(data:any): void {
        console.log("processHandler");
    }

    private errorHandler(error:any): void {
        console.log("errorHandler");
    }

    private completeHandler(data:any): void {
        console.log("completeHandler");
    }
}
new LayaSample();

2.2 GET

上面这个示例我们发送了一个简单的请求,方式是get方式。用来获取一个远端的文件,格式为文本的格式。假如我们动态请求远端数据可以改成如下格式:

this.hr = new HttpRequest();
this.hr.once(Event.PROGRESS, this, this.onHttpRequestProgress);
this.hr.once(Event.COMPLETE, this, this.onHttpRequestComplete);
this.hr.once(Event.ERROR, this, this.onHttpRequestError);
//发送了一个get请求,携带的参数为 name=myname 和 psword=xxx
this.hr.send('http://xkxz.zhonghao.huo.inner.layabox.com/', null, 'get', 'text');

这里的重点是send方法,这个send方法要和 XMLHttpRequest 的send区分开。

2.3 POST

下面用post方法请求一个数据方式如下:

this.hr = new HttpRequest();
this.hr.once(Event.PROGRESS, this, this.onHttpRequestProgress);
this.hr.once(Event.COMPLETE, this, this.onHttpRequestComplete);
this.hr.once(Event.ERROR, this, this.onHttpRequestError);
//发送了一个post请求,携带的参数为 name=myname 和 psword=xxx
this.hr.send('http://xkxz.zhonghao.huo.inner.layabox.com/', 'name=myname&psword=xxx', 'post', 'text');

注意:GET和POST请求是有区别的:

GET请求参数是通过URL进行传递的,POST请求的参数包含在请求体当中。

GET请求比POST请求更不安全,因为参数直接暴露在URL中,所以,GET请求不能用来传递敏感信息。

GET请求在url中传递的参数是有长度限制的(在HTTP协议中并没有对URL的长度进行限制,限制是特定的浏览器以及服务器对他的限制,不同浏览器限制的长度不同),POST对长度没有限制。

GET请求参数会完整的保留在浏览器的历史记录中,POST请求的参数不会保留。

GET请求进行url编码(百分号编码),POST请求支持多种编码方式。

2.4 扩展HttpRequest

在开发过程中 HttpRequest 可能不能满足我们的需求,比如上传文件,比如设置超时时间,比如操作表单数据等等。扩展 HttpRequest 很简单,你继承HttpRequest,或者干脆自己重写 HttpRequest 这个类都可以,这个看开发者的需求,重写 HttpRequest 建议直接继承 EventDispatcher。重写就是重新包装 XMLHttpRequest 这个类。下面是一个简单的继承的示范:

 class HttpRequestExtension extends Laya.HttpRequest {
     constructor() {
         super();
     }
     public send(url:string,data:any=null,method:string="get", responseType:string="text", headers:any=null):void{
         super.send(url,data,method,responseType,headers);
             this._http.upload.onprogress= function(e:any):void
             {
                 //上传进度
             }
             this._http.upload.onload= function(e:any):void
             {
             }
             this._http.upload.onerror= function(e:any):void
             {
             }
             this._http.upload.onabort = function(e:any):void
             {
             }
     }
 }

上面是一个上传文件的示范,添加了 XMLHttpRequest 的upload的一些事件,这里的 super.send 简单的用了父类的方法,开发者可以不用,完全自己另写一套来满足自己的需求。

三、 WebSocket连接

WebSocket是一种基于ws协议的技术,它使得建立双全工连接成为可能。websocket常见于浏览器中,但是这个协议不受使用平台的限制。

websocket发送数据的格式一般为二进制和字符串。LayaAir引擎已经为我们封装好了 Socket 和 Byte 类,收发数据结合Byte类就可以完成。

3.1 Laya.Sokcet

在LayaAir引擎中 Socket 就是我们使用 WebSocket 的基本类。 Socket 封装了 HTML5 WebSocket ,允许服务器端与客户端进行全双工(full-duplex)的实时通信,并且允许跨域通信。在建立连接后,服务器和 Browser/Client Agent 都能主动的向对方发送或接收文本和二进制数据。我们先来了解下 Socket 的用法

3.1.1 Connect 服务器

Socket 连接服务器有三种方式:

方式 说明
构造函数传参 立即连接 比如 new Socket(“192.168.1.2”,8899);注意这里的host参数没有ws前缀。
connect方法 传递url和端口号,连接服务器;socket.connect(“192.168.0.1.2”,8989);注意这里的host参数没有ws前缀。
connectByUrl方法 传递整个url,比如 socket.connectByUrl(“ws://localhost:8989”);这里有ws前缀。

3.1.2 发送数据

发送数据很简单,只需要调用Socket的send函数即可,参数可以是string或者是ArrayBuffer。

  • 发送字符串格式:
this.socket.send("hello world");//这是发送字符串的形式。
  • 发送二进制格式的数据:
//写入一个字节
this.byte.writeByte(1);
//写入一个int16的数据
this.byte.writeInt16(20);
//写入一个32位的浮点数据
this.byte.writeFloat32(20.5);
// 写入一个字符串;
this.byte.writeUTFString("hello");
//这里声明一个临时Byte类型
var by:Laya.Byte = new Laya.Byte();
//设置endian;
by.endian = Laya.Byte.LITTLE_ENDIAN;
//写入一个int32数据
by.writeInt32(5000);
//写入一个uint16 数据
by.writeUint16(16);
//把临时字节数据的数据写入byte中,这里注意写入的是by.buffer;
this.byte.writeArrayBuffer(by.buffer);
//这里是把字节数组的数据通过socket发送给服务器。
this.socket.send(this.byte.buffer);
//清除掉数据;方便下次读写;
this.byte.clear();

上面我们看到,通过一个字节数组把我们需要的数据读入一个Byte数组,最后发送给服务器的是byte.buffer,这是一个ArrayBuffer的数据类型。这里一定要注意send的参数是 ArrayBuffer,很多开发者可能不注意,直接传递成了Byte,导致发送数据不正确。假如写成 this.socket.send(this.byte);这是错误的,这点一定要注意。

3.1.3 接收数据

客户端从服务器接收到的数据都会派发到 Event.MESSAGE 监听函数中。receiveHandler的参数就是服务器发送回来的数据。可能是字符串,也可能是二进制ArrayBuffer。接收到的是字符串我们不用读,拿来直接用就可以。但是接收到的是二进制的话我们需要读取出来,转成我们需要的类型。

 private receiveHandler(msg: any = null): void {
   ///接收到数据触发函数
   //.............这里我们假设收到的是二进制ArrayBuffer
   this.byte.clear();
   this.byte.writeArrayBuffer(msg);//把接收到的二进制数据读进byte数组便于解析。
   this.byte.pos = 0;//设置偏移指针;
   ////下面开始读取数据,按照服务器传递过来的数据,按照顺序读取
   var a:number = this.byte.getByte();
   var b:number = this.byte.getInt16();
   var c:number = this.byte.getFloat32();
   var d:string = this.byte.getString();
   var e:string = this.byte.getUTFString();
 }

3.1.4 支持的事件类型

我们常用的基本就是连接建立成功,接收到数据,连接被关闭,出现异常后调度等

/**
 * 连接建立成功后调度。
 * @eventType Event.OPEN
 * */
/*[Event(name = "open", type = "laya.events.Event")]*/
/**
 * 接收到数据后调度。
 * @eventType Event.MESSAGE
 * */
/*[Event(name = "message", type = "laya.events.Event")]*/
/**
 * 连接被关闭后调度。
 * @eventType Event.CLOSE
 * */
/*[Event(name = "close", type = "laya.events.Event")]*/
/**
 * 出现异常后调度。
 * @eventType Event.ERROR
 * */
/*[Event(name = "error", type = "laya.events.Event")]*/

3.1.5 在代码中怎么使用

我们举一个简单的发送和接收数据的 WebSocket 代码示例:

    private connect(): void {

        //创建Socket对象
        this.socket = new Socket();

        //对服务器建立连接
        this.socket.connectByUrl("ws://echo.websocket.org:80");

        //表示需要发送至服务端的缓冲区中的数据
        this.output = this.socket.output;

        //添加监听事件
        this.socket.on(Event.OPEN, this, this.onSocketOpen);
        this.socket.on(Event.CLOSE, this, this.onSocketClose);
        this.socket.on(Event.MESSAGE, this, this.onMessageReveived);
        this.socket.on(Event.ERROR, this, this.onConnectError);
    }

    //连接建立成功回调
    private onSocketOpen(e: any = null): void {
        console.log("Connected");

        // 发送字符串
        this.socket.send("demonstrate <sendString>");

        // 使用output.writeByte发送
        var message: string = "demonstrate <output.writeByte>";
        for (var i: number = 0; i < message.length; ++i) {
            // 直接写缓冲区中的数据
            this.output.writeByte(message.charCodeAt(i));
        }

        // 发送缓冲区中的数据到服务器
        this.socket.flush();
    }

    // 连接断开后的事件回调
    private onSocketClose(e: any = null): void {
        console.log("Socket closed");
    }

    // 有数据接收时的事件回调
    private onMessageReveived(message: any = null): void {
        console.log("Message from server:");
        if (typeof (message) == 'string') {
            console.log(message);
        }
        else if (message instanceof ArrayBuffer) {
            console.log(new Byte(message).readUTFBytes());
        }
        // 清理缓存的服务端发来的数据
        this.socket.input.clear();
    }

    // 出现异常后的事件回调
    private onConnectError(e: Event = null): void {
        console.log("error");
    }

3.2 Laya.Byte 二进制读写

在开发项目中,二进制的操作是不可或缺的。在html5时代,对二进制的支持已经有了很大的突破。但是api的繁琐,对开发者开发项目来说不太方便。在页游时代,ActionScript3.0的二进制数组ByteArray,功能完善,api操作简单易懂,因此LayaAir的Byte在参考ByteArray的同时承接了html5的TypedArray类型化数组的特点。下面看下主要的用法

3.2.1 常用方法

  • 构造方法

    参数:

    length :长度

    当传入length参数时,一个内部数组缓冲区被创建,该缓存区的大小是传入的length大小。

    typedArray:类型化数组

    当传入一个包含任意类型元素的任意类型化数组对象(typedArray) (比如 Int32Array)作为参数时,typeArray被复制到一个新的类型数组。typeArray中的每个值会在复制到新的数组之前根据构造器进行转化。新的生成的类型化数组对象将会有跟传入的数组相同的length(比如原来的typeArray.length==2,那么新生成的数组的length也是2,只是数组中的每一项进行了转化)。

    ArrayBuffer:二进制数据缓冲区。

    上面的三种方法都可以实例化一个Byte,根据参数的不同创建二进制数据。

    //实例化一个二进制数组Byte
    var byte:Laya.Byte = new Laya.Byte();
    //或者传入一个类型化数组
    var uint8Byte:Uint8Array = new Uint8Array(10);
    var byte:Laya.Byte = new Laya.Byte(uint8Byte);
    //或者传入一个ArrayBuffer类型
    var buffer:ArrayBuffer = new ArrayBuffer(20);
    var byte:Laya.Byte = new Laya.Byte(buffer);
    
  • writeArrayBuffer(arraybuffer:*, offset:number = 0, length:number = 0):void

    写入指定的二进制缓冲数据。指定数据的偏移量和长度,如下:

    var byte:Laya.Byte = new Laya.Byte();
    var byte1:Laya.Byte = new Laya.Byte();
    byte1.writeFloat32(20.0);//写入一个四个字节的浮点数
    byte1.writeInt16(16);//写入一个两个字节的整数
    byte1.writeUTFString("hell world");//写入一个字符串;
    byte.writeArrayBuffer(byte1.buffer,6);//把byte1的数据从第六个字节开始读入byte中。省略其中的浮点数20.0和整数16
    byte.pos = 0;//
    console.log(byte.readUTFString())//从byte中读出字符串。
    
  • 读取数据

    getByte():number

    从字节流中读取带符号的字节。

    getInt16():number

    从字节流的当前字节偏移量位置处读取一个 Int16 值。

    getInt32():number

    从字节流的当前字节偏移量位置处读取一个 Int32 值。

    getFloat32():number

    从字节流的当前字节偏移位置处读取一个 IEEE 754 单精度(32 位)浮点数。

    getFloat32Array(start:number, len:number)any

    从指定的位置读取指定长度的数据用于创建一个 Float32Array 对象并返回此对象。

    getFloat64():number

    从字节流的当前字节偏移量位置处读取一个 IEEE 754 双精度(64 位)浮点数。

    getInt16():number

    从字节流的当前字节偏移量位置处读取一个 Int16 值。

    getInt32():number

    从字节流的当前字节偏移量位置处读取一个 Int32 值。

    getUint8():number

    从字节流的当前字节偏移量位置处读取一个 Uint8 值。

    getUint16():number

    从字节流的当前字节偏移量位置处读取一个 Uint16 值。

    getUint32():number

    从字节流的当前字节偏移量位置处读取一个 Uint32 值。

    getInt16Array(start:number, len:number):any

    从指定的位置读取指定长度的数据用于创建一个 Int16Array 对象并返回此对象。

    getString():string

    读取字符型值。

    getUTFBytes(len:number = -1):string

    读字符串,必须是 writeUTFBytes 方法写入的字符串。

    getUTFString():string

    读取 UTF-8 字符串。

  • 写入数据

    writeByte(value:number):void在字节流中写入一个字节。

 var byte:Laya.Byte = new Laya.Byte(); 
 byte.writeByte(10);//0-255之间

writeFloat32(value:number):void在当前字节偏移量位置处写入 Float32 值。范围是$\left[-2^{128}, 2^{127}\right]$,约为-3.4E38—3.4E+38。

var byte:Laya.Byte = new Laya.Byte();
byte.writeFloat32(10.021);

writeFloat64(value:number):void写入float64位数值 其数值范围为-1.7E308~1.7E+308。

writeInt16(value:number):void在当前字节偏移量位置处写入 Int16 值。范围-32768 到 +32767之间。

var byte:Laya.Byte = new Laya.Byte();
byte.writeInt16(120);

writeInt32(value:number):void在当前字节偏移量位置处写入 Int32 值。-2,147,483,648 到 +2,147,483,647 之间的有符号整数。

 **writeUint16**(value:number):void在当前字节偏移量位置处写入 Uint16 值。

writeUint32(value:number):void在当前字节偏移量位置处写入 Uint32 值。

writeUint8(value:number):void在当前字节偏移量位置处写入 Uint8 值。

writeUTFBytes(value:string):void写入字符串,该方法写的字符串要使用 readUTFBytes 方法读取。

writeUTFString(value:string):void将 UTF-8 字符串写入字节流。

  • clear():void清除数据。

    var byte:Laya.Byte = new Laya.Byte();
    byte.writeInt16(120);
    byte.pos =0;//读取位置归零。
    
  • getSystemEndian():string[static]获取系统的字节存储顺序。

    console.log(Laya.Byte.getSystemEndian());//打印系统的字节顺序
    

3.2.2 属性

  • BIG_ENDIAN : string= bigEndian[static] 表示多字节数字的最高有效字节位于字节序列的最前面。

  • LITTLE_ENDIAN : string= littleEndian[static] 表示多字节数字的最低有效字节位于字节序列的最前面。

  • posnumber当前读取到的位置。

    var byte:Laya.Byte = new Laya.Byte();
    byte.writeInt16(120);
    byte.pos =0;//读取位置归零。
    
  • length: number字节长度。

  • endian : string字节顺序。

    var byte:Laya.Byte = new Laya.Byte();
    byte.endian = Laya.Byte.BIG_ENDIAN;//设置为大端;
    
  • bytesAvailable : number[read-only]可从字节流的当前位置到末尾读取的数据的字节数。

    var byte:Laya.Byte = new Laya.Byte();
    byte.writeFloat32(20.0);
    byte.writeInt16(16);
    byte.writeUTFString("hell world");
    byte.pos = 6;
    console.log(byte.bytesAvailable)
    

3.2.3 代码演示

下面我们通过一个完整的代码来演示下这个类的应用,比如网络连接中,我们接收和发送网络消息。

var msg:any ={name:"xxx",age:18,weight:65.5,height:175};
var byte:Laya.Byte = new Laya.Byte();
//实例化byte数组
byte.endian = Laya.Byte.LITTLE_ENDIAN;
//设置大小端
byte.writeUTFString(msg.name);
//写入数据
byte.writeByte(msg.age);
byte.writeFloat32(msg.weight);
byte.writeInt16(msg.height);

输出看下结果:

//设置pos为0 开始从头开始按照写入的顺序读取读取
byte.pos = 0;
console.log(byte.getUTFString());
console.log(byte.getByte());
console.log(byte.getFloat32());
console.log(byte.getInt16());

3.2.4 类型化数组

Laya的byte封装的就是类型化数组,开发者可以参考mdn的官方api说明。来扩展自己的项目的应用。

  • DataView 视图提供了一个与平台中字节在内存中的排列顺序(字节序)无关的从ArrayBuffer读写多数字类型的底层接口。
  • Uint8Array 数组类型表示一个8位无符号整型数组,创建时内容被初始化为0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。
  • Int8Array :类型数组表示二进制补码8位有符号整数的数组。内容初始化为0。 一旦建立,你可以使用对象的方法引用数组中的元素,或使用标准数组索引语法。
  • Int16Array();类型数组表示二进制补码16位有符号的数组。
  • Uint16Array();类型数组表示二进制补码16位无符号的数组
  • Int32Array();类型数组表示二进制补码32位有符号的数组
  • Uint32Array();类型数组表示二进制补码32位无符号的数组
  • Float32Array();类型数组表示32位浮点数数组。
  • Float64Array();类型数组表示64位浮点数数组。

四、ProtocolBuffer使用

protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。类似于XML,JSON这样的数据表示语言,ProtocolBuffer是用于结构化数据串行化的灵活、高效、自动的方法,格式有点类似XML,可以自己定义数据格式,它是一种二进制格式允许你使用规范的语言定义一个模式。

ProtocolBuffer 作为网络通信的协议格式,是现在一种非常流行的方式,下来我们来了解一下。

4.1 Message 定义

这里简单的给出一个例子,是一个非常简单的请求的message格式

// awesome.proto
package awesomepackage;

//指定proto版本
syntax = "proto3";

//message包含多个种类的fields
message AwesomeMessage {
    string awesome_field = 1; // becomes awesomeField
}

上述例子中fields的种类是字符串型的(string),当然也可以指定更加复杂的fields,比如枚举类型enum,或者是嵌套的message类型

上述Message定义好之后,我们把协议文件保存到项目目录中

"assets/res/protobuf/awesome.proto";

4.2 项目中添加protobuf 类库

我们可以从 https://github.com/protobufjs/protobuf.js 下载最新 protobuf 类库,并放到项目的bin目录中,同时在index.html 中引用到 protobuf.js

    <script type="text/javascript" src="protobuf.js"></script>

4.3 加载协议文件

protobuf 类库,通过 load 方法来加载协议文件

/**
 * Loads one or multiple .proto or preprocessed .json files into a common root namespace and calls the callback.
 * @param {string|string[]} filename One or multiple files to load
 * @param {Root} root Root namespace, defaults to create a new one if omitted.
 * @param {LoadCallback} callback Callback function
 * @returns {undefined}
 * @see {@link Root#load}
 */
function load(filename, root, callback) {
    if (typeof root === "function") {
        callback = root;
        root = new protobuf.Root();
    } else if (!root)
        root = new protobuf.Root();
    return root.load(filename, callback);
}

4.4 Message 方法

  • Message.verify(message: Object): null|string

验证一个Message对象是否满足有效消息的要求。

  • Message.create(properties: Object): Message

对满足有效消息要求的一组Javascirpt数据创建新消息实例。

  • Message.encode(message: Message|Object , writer: Writer): Writer

对Message对象进行编码,用于网络通信传输。

  • Message.decode(reader: Reader|Uint8Array): Message

网络通信传输数据中,解码获得Mesaage对象。

  • Message.toObject(message: Message , options: ConversionOptions): Object

转换Message对象数据到一组Javascirpt数据。

4.5 代码示例

    onAwake(): void {

        var resPath: string = "assets/res/protobuf/awesome.proto";
        // 加载protobuf文件
        this.ProtoBuf.load(resPath, this.onAssetsLoaded);
    }

    private onAssetsLoaded(err: any, root: any): void {
        if (err)
            throw err;

        // 获得一个Message消息类型
        var AwesomeMessage: any = root.lookupType("awesomepackage.AwesomeMessage");

        console.log(AwesomeMessage);

        // 初始化数据
        var payload: any = { awesomeField: "AwesomeString" };
        console.log(payload);

        // 验证数据是否有效
        var errMsg: any = AwesomeMessage.verify(payload);

        // 如果有异常抛出异常并终止
        if (errMsg)
            throw Error(errMsg);

        // 创建Message的实体
        var message: any = AwesomeMessage.create(payload);
        console.log(message);

        // 编译Message实体成 Buffer 数据格式,等待发送数据
        var buffer: any = AwesomeMessage.encode(message).finish();
        console.log(buffer);

    }
Copyright ©Layabox 2022 all right reserved,powered by LayaAir Engine更新时间: 2024-10-14 20:42:16

results matching ""

    No results matching ""