在Egret项目中使用protobuf

protobuf简介

ProtocolBuffer是用于结构化数据串行化的灵活、高效、自动的方法,有如XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。

使用protobuf

下载protobuf的js库

下载地址:http://download.csdn.net/download/yue19870813/9957415

解压后包括如下几个文件:
- ByteBufferAB.min.js
- Long.min.js
- protobuf.d.json
- protobuf.d.ts
- ProtoBuf.min.js

编译成Egret项目可以使用的库文件

白鹭官方第三方库使用文档:http://developer.egret.com/cn/github/egret-docs/extension/threes/instructions/index.html

创建第三方模块

当我们准备好了要用的第三方库的源文件后,还需要把它编译成 egret 需要用的第三方库。以我们上面下载的protobuf文件为例。
- 创建一个egret第三方库的项目文件,在命令行中输入:

egret create_lib protobuf

注意:第三方库项目与 Egret 项目不能嵌套。请不要在Egret 项目目录下面创建第三方库项目。

  • 运行以后会在目录下生成几个空文件夹 bin、src、libs(如果没有请自行加上),还有一个 package.json 的配置文件。
  • 把准备好的几个文件拷贝到src文件夹中。
  • 打开package.json文件,将要编译的protobuf文件配置进去,注意前后依赖关系:
{
    "name": "egret",
    "version": "3.0.8",
    "modules": [
        {
            "name": "protobuf",
            "description": "protobuf",
            "files": [
                "Long.min.js",
                "ByteBufferAB.min.js",
                "ProtoBuf.min.js",
                "protobuf.d.ts"
            ],
            "root": "src"
        }
    ]
}
  • 最后在命令行中输入编译命令:
egret build protobuf
  • 编译完成后会在bin目录下生成我们项目中需要使用的几个文件。

使用第三方模块

和官方的模块使用方式相同,在egretProperties.json中配置:

{
  "native": {
    "path_ignore": []
  },
  "publish": {
    "web": 0,
    "native": 1
  },
  "egret_version": "4.0.1",
  "modules": [
    {
      "name": "egret"
    },
    {
      "name": "socket"
    },
    {
      //第三方库的名称
      "name": "protobuf",   
      //刚才我们创建的第三方库的路径,绝对路径或者相对路径
      "path": "../protobuf"
    }
  ]
}

重新编译项目,如果没有报错,正常情况下protobuf库就算是引用到了项目中,后面就可以直接使用了。

定义消息结构体

关于protobuf消息定义的详细介绍:http://www.jianshu.com/p/b1f18240f0c7

我们这里定义两个结构体,一个用于向服务器发送消息,另一个用于接收消息来解析。

//向服务器发送的消息
message SendMsg {
    required uint32 id = 1;     //int类型
    required string name = 2;   //字符串类型
}

//服务器发送的消息,我们会用protobuf来解析
message BackMsg {
    required uint32 type = 1;   //int类型
    required SendMsg back = 2;  //返回一个结构体数组
}
//在protobuf的消息定义中支持消息嵌套,详细的定义方式参照上面发的连接。

创建protobuf对象发给服务器

//初始化消息体
var builder:any = dcodeIO.ProtoBuf.loadProto("上一步声明的消息结构文本");  
//构建SendMsg协议对象
var clazz = builder.build("SendMsg"); 
var data = new clazz();  
var arraybuffer:ArrayBuffer = data.toArrayBuffer();
var buf = new egret.ByteArray(arraybuffer);
//创建ByteArray数组用来保存消息对象并发送到网络
var mss = new egret.ByteArray();
//写入消息体
mss.writeBytes(buf);
//创建websocket对象
var ws = new Websocket();   //此行为伪代码
//将消息写到服务器
ws.writeBytes(mss); 

解析服务器发送过来的protobuf对象

//socket接收的数据(_socket是前置已经连接的)
let btyearray: egret.ByteArray = new egret.ByteArray();
this._socket.readBytes(btyearray);
this._onReceive && this._onReceive(btyearray);

//读取服务器发送过来的字节数据
let msgBuff: ArrayBuffer;
let barr: egret.ByteArray = new egret.ByteArray();
btyearray.readBytes(barr);

let len = barr.buffer.byteLength;
let dataView = new DataView(barr.buffer);
let pbView = new DataView(new ArrayBuffer(len));
for (let i = 0;i < len;i++) {
    pbView.setInt8(i,dataView.getInt8(i));
}
msgBuff = pbView.buffer;

//解析消息内容
var builder:any = dcodeIO.ProtoBuf.loadProto("上一步声明的消息结构文本");   
var clazz = builder.build("BackMsg");     
var data = clazz.decode(msgBuff);  

//获取数值
var type = data.get("type");
var smsg = data.get("back");
var id = smsg.id;
var name = smsg.name;

将protobuf的发送和接收工具化

这样做之后发现,使用protobuf后还是有些不方便,还是要手动写很多解析代码,这里我们可以将上面两步的内容工具话,代码自动生成,下面就把一个用Python写的一个代码生成脚本贡献出来,其中还包括了服务端用的java代码生成。

下载地址是:http://download.csdn.net/download/yue19870813/9959227

使用方法

  • 使用前阅读使用必读.txt
  • 在脚本的同级目录创建protofiles目录用于存放*.proto消息定义文件。
  • 比如使用改工具生成上面定义的两条消息的ts文件内容将会是下面这样:
// SendMsg
class SendMsgMessage extends MessageBase {
    private _data:any = null;
    private _clazz:any = null;

    public constructor() {
        super();  
        var builder:any = dcodeIO.ProtoBuf.
        loadProto("消息定义文本内容");   
        this._clazz = builder.build("SendMsg");     
    }           

    public setId(id:any):void {
        this._data.set("id", id);
    }

    public getId():any {
        return this._data.get("id");
    }

    public setName(name:any):void {
        this._data.set("name", name);
    }

    public getName():any {
        return this._data.get("name");
    }

    public getPID():number {
        return 3001;
    }

    public initData():void {                
        this._data = new this._clazz();  
    }

    public setData(buff:egret.ByteArray):void {
        this._data = this._clazz.decode(buff);  
    }

    public toByteArray():egret.ByteArray {
        var arraybuffer: ArrayBuffer = this._data.toArrayBuffer();
        return new egret.ByteArray(arraybuffer);
    }
}

// BackMsg
class BackMsgMessage extends MessageBase {
    private _data:any = null;
    private _clazz:any = null;

    public constructor() {
        super();  
        var builder:any = dcodeIO.ProtoBuf.
        loadProto("消息定义文本内容");   
        this._clazz = builder.build("BackMsg");     
    }           

    public setType(type:any):void {
        this._data.set("type", type);
    }

    public getType():any {
        return this._data.get("type");
    }

    public setBack(back:any):void {
        this._data.set("back", back);
    }

    public getBack():any {
        return this._data.get("back");
    }

    public getPID():number {
        return 3002;
    }

    public initData():void {                
        this._data = new this._clazz();  
    }

    public setData(buff:egret.ByteArray):void {
        this._data = this._clazz.decode(buff);  
    }

    public toByteArray():egret.ByteArray {
        var arraybuffer: ArrayBuffer = this._data.toArrayBuffer();
        return new egret.ByteArray(arraybuffer);
    }
}
  • 使用:
//创建消息协议
var sendMsg = new SendMsgMessage();
sendMsg.initData();
sendMsg.setId(1);
sendMsg.setName("Tim");
var sendBuf = sendMsg.toByteArray();
//可以通过websocket或者http发送sendBuf到服务器。

//解析消息时
var backBuf; //假设这是服务器返回的数据
var backMsg = new BackMsgMessage();
backMsg.setData(backBuf);

var type = backMsg.getType();
var back = backMsg.getBack();
var id = back.getId();
var name = back.getName();

其中优化及可以优化的部分

  • 将protobuf完全对象化,在项目中就是创建对象来使用。
  • 可以在协议中增加标签来声明该protobuf是否需要缓存。鉴于protobuf的序列化还是比较耗时的,建议使用频繁的消息要缓存。
  • 内置嵌套并且不单独使用的消息协议可以通过标签配置不生成对应的ts代码文件,以减小文件体积和运行时内存。
  • 每个protobuf对象都有对应的pid用于区分查找,后续会写一套完整的工具类,方便在项目中零成本使用protobuf。
  • 目前生成的ts脚本文件相对内容复杂,体积后续可以优化。
  • 声明文件中有两种标签:
//_no_cache : 不做缓存
//_c_noNeed :不需要生成ts代码
这两中标签使用会在后续完整工作流中介绍。

后记

本文仅结束了protobuf对象的创建和使用及工具化。但这套生成的机制预留了很多接口和标签后续会针对这些有一套完整的通信协议的工作流在项目中使用。大家可以持续关注。

如果对我的文章感兴趣可以关注我的公众号:

你可能感兴趣的:(javascript,程序设计)