对protobuf认识,总结为以下几点:
1. pb是一种编码方式。
之所以这么说是因为有的人认为它是协议,那就侠义化了,pb本质上就和json、xml类似,是一种编码方式,用pb编码出来的流可以套在任何现有协议里面,作为内容部分,如可以放在http的content区域,或者私有协议的content区域,外面套上(length, type, flag,seqno)等,这样就很容易实现加密压缩。有人说pb不提供加密压缩,无法使用或不好使用,其实这恰恰是它的灵活性所在,他没有限制你如何使用,你可以各种方式灵活组合。将这个content放http内容区也一样,在header里面指定mime type或添加特殊关键字即可。
2. pb是一种tlv的实现。
早期的tlv是标准的type length value,此时没有id,后来有id了,是为了解决协议变动时的向前向后兼容性,但这个也不是pb的独创,早先已经有很多这种用法了,如今tlv的t就是tag,一般指(id, type),length则是个可选参数,根据type有时length是固定的,此时length可省略。
protobuf就是tlv(tag length value)编码方式的一种实现,内部就是(id, type+[length], value)的组合,有的type默认了length,此时length可省略,不定的时候就需要length,本质上就是这样,pb并不是什么很新奇的东西,其实我们很多人之前做过类似的tlv编码。
3. pb的核心价值在于提供了一套工具,简化了多语言交互的复杂度,使得编码解码工作有了生产力。
很多中小团队也做过tlv编码,但可能只做了一种语言的编解码器,也没有提供工具,但pb提供了一组工具,支持了很多语言,支持力度比较好,一般个人或小团队做不到这个程度,有工具了其实就有生产力了,很多开发人员并不太懂这个道理,总以为你给我这个需求我就能解决,但这个解决时间在有工具和没有工具的情况下是差很多的。
4. pb是非自描述的。
pb由于只存了(id, type,length, value),所以并不是自描述的,对应字段的只有id,而该id的具体细节在定义文件.proto里面,脱离了proto你要理解这一段东西是有些难度的,不过也因为有了id,所以还是有一定的描述性,并不是完全不可读的,但pb的可读性比json、xml差了一个档次。很多人搞不懂为何要强调自描述可读性,特别是用c/c++语言的开发人员,总喜欢强调效率,其实效率并不是总是最迫切的,不管是编解码时间还是编码出来的字节尺寸,很多时候这都不是问题,反而可读性是问题,此时使用可读性高的json、xml等相比pb优势非常明显。
5. pb编码效率很高,但并没用尽所有余量。
就举两个来说吧,pb里面var type类型存的是按照7位编码的,此时可能会让一个满编码的4字节变成5byte,满字节的8字节会编码为10字节,此时就不如将前面放一个描述length的字节,或者将这个长度合并在前一个描述id和type的字节里面。另一个就是float、double的存储,它并没有采取太多有效的压缩方式,事实上也是可压缩存储的。
虽然并没有把存储效率用尽,但和json、xml等文本编码方式相比,pb的编码依然是相当高效的,这是毋庸置疑的。
6. pb依然是小众的。
说pb是小众是和json、xml相比的,xml是工业标准,json由于其足够简单,也有众多拥护者,特别是web环境中,由于js对json的天然支持,json可算是无冕之王了,如果要考虑和其他系统交互,pb相比json、xml等相差甚远,使用的时候一定要仔细考虑清楚。因为哪怕是json,对一个典型的json流编解码速度也能达到单线程30万/秒左右,大多数情况下这早就不是瓶颈了,虽然耗cpu会比pb略高一点,字节也会略多一点,但可读性,多平台兼容性远非pb可比。
- 总结了一下使用的过程和方法如下:
- 下载protobuf-2.3.0:
- http://protobuf.googlecode.com/files/protobuf-2.3.0.zip
- 安装:
- unzip protobuf-2.3.0.zip
- cd protobuf-2.3.0
- ./configure
- make
- make check
- make install
- 结果:
- Libraries have been installed in:
- /usr/local/lib
- Head files hava been installed in:
- /usr/local/include/google/
- protobuf/
- 开始写.proto文件:
- BaseMessage.proto:
- message MessageBase
- {
- required int32 opcode = 1;
- // other: sendMgrId, sendId, recvMgrId, recvId, ...
- }
- message BaseMessage
- {
- required MessageBase msgbase = 1;
- }
- BaseMessage.proto是其它消息proto文件的基础,以容器模块的C2S_GetContainerInfo为例:
- ContainerMessage.proto:
- import "BaseMessage.proto";
- message C2SGetContainerInfoMsg
- {
- required MessageBase msgbase = 1;
- optional int32 containerType = 2;
- }
- .proto文件编写规则:
- 1)所有消息都需要包含msgbase这项,并编号都为1,即:
- required MessageBase msgbase = 1;
- 2)除了msgbase这项写成required外,其它所有项都写成optional。
- 编译 .proto 文件
- protoc -I=. --cpp_out=. BaseMessage.proto
- protoc -I=. --cpp_out=. ContainerMessage.proto
- 生成BaseMessage.pb.h、BaseMessage.pb.cc
- ContainerMessage.pb.h、ContainerMessage.pb.cc
- 将它们添加到工程文件中。
- 编写C++代码:
- 1)发送消息:
- C2SGetContainerInfoMsg msg;
- msg.mutable_msgbase()->set_opcode(C2S_GetContainerInfo);
- msg.set_containertype(1);
- std::string out = msg.SerializeAsString();
- send(sockfd, out.c_str(), out.size(), 0);
- 2)接收消息
- char buf[MAXBUF + 1];
- int len;
- bzero(buf, MAXBUF + 1);
- len = recv(new_fd, buf, MAXBUF, 0);
- if (len > 0)
- {
- printf("%d接收消息成功:'%s',共%d个字节的数据/n",
- new_fd, buf, len);
- BaseMessage baseMsg;
- std::string data = buf;
- baseMsg.ParseFromString(data);
- int opcode = baseMsg.mutable_msgbase()->opcode();
- printf("opcode=%d/n", opcode);
- switch (opcode)
- {
- case C2S_GetContainerInfo:
- {
- C2SGetContainerInfoMsg msg;
- msg.ParseFromString(data);
- printf("containerType=%d/n", msg.containertype());
- break;
- }
- default:
- {
- break;
- }
- }
- }
- else
- {
- if (len < 0)
- printf("消息接收失败!错误代码是%d,错误信息是'%s'/n",
- errno, strerror(errno));
- close(new_fd);
- return -1;
- }
- 编译C++代码:
- Need to link lib:
- protobuf
- pthread
- 参考:
- 1,http://www.360doc.com/content/10/0822/16/11586_47942017.shtml
- 2,http://code.google.com/p/protobuf/