美文网首页我爱编程程序员
ProtoBuf 协议设计与开发

ProtoBuf 协议设计与开发

作者: David_Longzy | 来源:发表于2018-04-16 12:16 被阅读0次

    周日本来要去爬山的,但是没去成,突然想写点东西,但本人文采不好,只能闲扯一点技术方面的文章,整理了下有道笔记,然后最近一直在开发protobuf的协议接口,就写写ProtoBuf相关的东西吧。

    本文精髓:

       protobuf的消息设计

       消息分发设计Message Dispatch

       针对程序升级的proto设计

    文章末尾对着三点做详细说明。

         最近一段时间在写linux服务端接口程序,刚开始如果按照需求的话,大概有十个接口左右,但是后面慢慢分解需求后,其实真正就只有五个接口。

        编码工作早早完成,进入到测试阶段,一般情况可能会等web实现完成后,然后借助web client,在做调试,但是这期间工作效率不高,而且存在很多问题,可能后台接口没实现好,也有可能web没实现好。作为linux后台服务开发,需要会模拟客户单发送数据,如果接口很简单的话,可以直接使用telnet工具。

        现在最为流行的后台服务端通信的协议有:JSON、XML、ProtoBuf,当协议为这三种的时候,简单的telnet就不能胜任了,使用JSON和ProtoBuf的话,先借助工具做序列化工作,使用XML的话,也要事先编写好XML。

        为了更加高效的对后台服务接口做好单元测试,也为了在web开发调试的时候,提供稳定的后台服务接口,自己利用MFC写了一个小的调试工具

    参数设置里面输入的是json串,因为这个有很多工具方便序列化,如:https://www.bejson.com/jsoneditoronline/

        这个工具很简单,从界面就三个输入,IP、port、消息类型,主要工作就是模拟客户端向服务端发送消息:

            需要借用服务端的proto协议文件

            序列化protobuf

            根据消息类型做消息分发

        这里用到protobuf,先大概说一下它的使用与原理

        1,介绍安装

            直接去百度,这里就跳过

        2,编写 .proto文件

            来个例子:

            package lm;

            message helloworld

            {

            required int32     id = 1;  // ID

            required string    str = 2;  // str

            optional int32     opt = 3;  //optional field

            }

    限定修饰符 + 数据类型 + 字段名称 = 字段编码值

    尽量养成良好测编程习惯,对proto文件命名与消息名做规范的命名

        在上例中,package 名字叫做 lm,定义了一个消息 helloworld,该消息有三个成员,类型为 int32 的 id,另一个为类型为 string 的成员 str。opt 是一个可选的成员,即消息中可以不包含该成员。

        3,编译.proto文件

        写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。本例中我们将使用 C++。假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:

    protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

    命令将生成两个文件:

            lm.helloworld.pb.h , 定义了 C++ 类的头文件

            lm.helloworld.pb.cc , C++ 类的实现文件

        在生成的头文件中,定义了一个 C++ 类 helloworld

        4,序列化

        在第三部编译生成的cc文件中,有一系列的SerializeToXXX方法,如SerializeToArray,可以根据具体情况用这一系列方法进行序列化。

        5,反序列化

        在第3个步骤编译生成的cc文件中,有一系列的ParseFromXXX方法,如ParseFromArray,可以根据具体情况用这一系列方法进行反序列化。

        掌握以上几个步骤基本就能搞定proto的开发了,进阶的话可以去掌握proto的数据类型以及requried、optional、repeated限定修饰符,和message的嵌套(message嵌套可以设计出更多复杂的协议,满足更复杂的需求)。

        protobuf的优劣自己去百度,JSON,XML我也有用过,但是相对来说,谷歌的protobuf是我用起来最方便高效的。

    这是网上的一个测试结果:http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

        回到文章开头部分说的精髓,现在逐一到来

        一,proto的设计:

        一般设计规则如下

        message Request

        {

        required fixed64 msgtype = 1;

        required bytes bodys = 2;

        }

        一个消息类型加一个消息体,但这不能满足复杂的业务需求,所以复杂的系统里面一般拆成这样:

        message Header

        {

        required fixed64 msgtype = 1;

        }

        message HelloworldRequest

        {

        required int32     id = 1;  // ID

        required string    str = 2;  // str

        optional int32     opt = 3;  //optional field

        }

        一个消息类型(单独定义一个文件)对应一个请求消息,一个请求消息对应一个接口,消息里面的字段对应接口所需要的参数,这是常用的设计方法,能满足所有的业务需求。

        二,消息分发的设计

    这里的message dispatch是指程序根据不同的msgtype,反序列化proto和做不同的业务逻辑处理。

        古老而又传统的设计是采用switch case来做(我曾经看到有在用if else的),这种方法有很多不足之处,我这里举几个很常见的:

        1,代码臃肿,随着消息类型的增加,会有n多的case

        2,假设case里漏掉了break;那就不妙了。

        3,代码维护差,每次增加msgtype,除了实现对应的业务逻辑处理,还要到消息入口增加对应的case。

    在c语言里面,有一种很实用的办法,那就是函数指针,很多开源的和上层应用的回调函数或方法的底层都是封装了c语言的函数指针,这里提到函数指针,我简单介绍一下(本文没有用很大篇幅来说明函数指针,掌握其基本定义,慢慢学会衍生到复杂的概念):

            从概念上说,函数指针是指向函数的指针变量,它本质上是一个指针变量。

            其广泛的定义是: int (*f) (int x);

            复制和调用: int func(int x);   f = func;

        那么函数指针在Message Dispath 如何设计呢,还是来一个简单的例子:

        定义一个结构体

            struct SMsgCmd {

            int msgtype;                             /*msg type*/

            int (*func)(const char *argv);           /* handler * 函数指针/

            };

        定义一个结构体数组,并初始化

            static struct SMsgCmd commands[] = {

            { 1,   Request1},

            { 2,   Request1},

            };

        在服务程序的消息入口处,遍历改数组即可

            for ()

            {

            if (msgtype == commands[i].msgtype){

            (*commands[i].func)(argv);

            }

            }

    在c++11里面,可以利用std::function 和std::bind,其原理跟函数指针一个道理。

        三,针对程序升级的proto设计

        程序升级是常有的事,但我们升级的时候需要考虑兼容性,之前有看到过同个版本号如

        if (version == 1){

        }

        else if (version > 2)

        .........

        这样做的缺陷我就不在过多的说明了。

        对于proto的协议来说,我们只要做到以下几点,就会完美兼容新旧版本

    1,不要随意添加或删除 required限定词修饰的字段

            2,不要随意改变现有字段编码值

            3,若需要新增字段,请用optional限定词修饰

    本文到此就算结束了,若有错误之处,请多多指教!

    欢迎关注本人微信公众号:lzyTalk江湖,不只是谈江湖,还会分享很多技术干货哦!

    相关文章

      网友评论

        本文标题:ProtoBuf 协议设计与开发

        本文链接:https://www.haomeiwen.com/subject/zyoskftx.html