ProtoBuffer消息设计经验

来源:互联网 发布:淘宝冷光美白仪有用么 编辑:程序博客网 时间:2024/05/21 07:13

message设计

分类

消息用于client与server之间的交互,一般都是请求-响应的组合。写代码的一大原则是:类、函数等的命名尽量能体现其含义。所以,个人习惯在消息编号以及消息结构体中通过后缀REQ、Req、RSP、Rsp来却分请求、响应。有的响应没有响应内容,无须定义响应的message,这个就相当于一个ack消息。所以可以通过后缀来表示message的分类:

_REQ、Req:表示请求
_RSP、Rsp:表示请求的响应,带有响应消息
_ACK、Ack:表示请求的确认,无响应消息

例如:

message Test1Req{        optional uint64 uid    = 1;      optional uint32 type    = 2; }message Test1Rsp{    optional int32 test    = 1;     }message Test2Req{        optional uint64 uid    = 1;      optional uint32 type    = 2; }message Test2Rsp{    optional int32 test    = 1;     }

抽象

消息往往会有很多公用的字段,特别是响应的消息,比如消息类型、消息的序列号、响应的结果(错误码)、响应结果的描述等,可以定义一个总的Message,将公共的字段提取出来放到Message中。例如:

message Message{   required MAGIC type = 1;   required fixed32 seq = 2;   optional fixed32 errCode = 3;   optional bytes errMsg = 4;   optional Test1Req test1Req = 5;   optional Test1Rsp test1Rsp = 6;   optional Test2Req test2Req = 7;   optional Test2Rsp test2Rsp = 8;}

这样做的好处是:

(1)更加抽象
(2)所有消息针对Message parse一次即可,不需要再分别去parse test1Req、test1Rsp、test2Req、test2Rsp

上述代码中,请求中一般没有errcode字段,所以可以进一步抽象出Request和Response两个消息:

message Request{    optional Test1Req test1Req = 1;    optional Test2Req test2Req = 2;}message Response{    required fixed32 errCode = 1;    optional bytes errMsg = 2;    optional Test1Rsp test1Rsp = 1;    optional Test2Rsp test2Rsp = 2;}

这样的话,如有需要,请求的公用字段都加到Request中,响应的公用字段都加到Response中,两者相同的公用字段都加到上层的Message中。Message就变成如下:

message Message{   required MAGIC type = 1;//消息类型,MAGIC枚举   required fixed32 seq = 2;   optional Request request = 3;   optional Response response = 4;}

使用enum定义message的类型

接下来要为每一个消息定义一个消息类型,一般建议用enum定义,例如定义一个MAGIC的enum:

enum MAGIC{    MAGIC_TEST1_REQ = 0x00001001;    MAGIC_TEST1_RSP = 0x00001002;    MAGIC_TEST2_REQ  = 0x00001003;    MAGIC_TEST2_RSP  = 0x00001004;}

本人喜欢用MAGIC这个命名。MAGIC(幻数)是计算机编码中的一个术语,直接翻译为魔幻一般的数字,多好听。
至于类型的数值,可以自定义,比如从1开始递增。

这样定义完后,拿到protobuffer后的字符串,一次解析,再根据type即可取出响应的对象

    //代码片段          msg := Message{}    err := proto.Unmarshal(buf[:len], &msg)    if err != nil {        //错误处理        return err    }    if msg.GetType() ==  MAGIC_TEST1_REQ{        req := msg.GetRequest().GetTest1Req()//直接取出Test1Req对象        //handle    }    if msg.GetType() ==  MAGIC_TEST2_REQ{        req := msg.GetRequest().GetTest2Req()//直接取出Test2Req对象        //handle    }    if msg.GetType() ==  MAGIC_TEST1_RSP{        rsp := msg.GetRequest().GetTest1Rsp()//直接取出Test1Rsp对象        //handle    }

错误码定义

错误码定义也用enum。错误码定义的原则是:通过错误码就能直接识别是哪个模块产生的,这样定位问题的时候就直指目标。可以通过数字区间来定义不同模块的错误码:

enum ERR_CODE {       CLIENT_ERR_REFUSE = 100001;//客户端的错误码从100000-19999       CLIENT_ERR_ACCEPT = 100002;       SERVER1_ERR_STOP = 200001;//模块1的错误码从200000-29999       SERVER2_ERR_OVERLOAD = 300001;//模块2的错误码从300000-39999 }

序列化后数据的封包和解包

  • 封包:定义好的Message对象通过encode之后序列化成一段buf(字符串),加上长度一起封包。一般包头前两个字节用来标示buf的长度,后面接上buf。然后发送给对端。

  • 解包:取前两个字节先解出长度,再读入相应长度的buf,parse成Message对象即可。

proto的维护

实际应用中,不同的模块增加、修改消息或者字段是很频繁的事情。如果多个人修改proto文件,容易导致不同模块上运行不同版本proto,会有一些不可预估的风险。所以一般指定一个人负责proto的维护,所有proto的修改都由一个人完成,且制定proto更新时涉及模块的发版顺序。

原创粉丝点击