ProtoBuf 协议设计与开发

周日本来要去爬山的,但是没去成,突然想写点东西,但本人文采不好,只能闲扯一点技术方面的文章,整理了下有道笔记,然后最近一直在开发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江湖,不只是谈江湖,还会分享很多技术干货哦!


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 由于工程项目中拟采用一种简便高效的数据交换格式,百度了一下发现除了采用 xml、JSON 还有 ProtoBuf(...
    黄海佳阅读 48,482评论 1 23
  • 转自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志阅读 24,762评论 2 38
  • 我们身处的蓝色星球,不过是浩瀚宇宙中的一粒尘埃。每个你爱过的人、每个你听说过的人、每个曾经存在过的人,都在...
    炫彩和音阅读 353评论 0 0
  • 在一个开心的中午,因为喵喵家好久没有那么热闹了,喵喵想:“一家人都在,真好”。 那天,喵喵的妈妈在洗衣...
    星同百分百阅读 197评论 0 0
  • 摘录有一个缺点,即使整段的摘录文章,在少了上文铺垫的情况下都会对理解文章中的寓意大打折扣。不过现在大家的生活节奏都...
    王赫麟阅读 557评论 0 0