读书人

陈巨大牛关于protobuf的分析

发布时间: 2012-10-29 10:03:53 作者: rapoo

陈硕大牛关于protobuf的分析

http://www.cnblogs.com/Solstice/archive/2011/04/03/2004458.html

?

我估计大家通常关心和使用的是图的左半部分:MessageLite、Message、Generated Message Types (Person, AddressBook) 等,而较少注意到图的右半部分:Descriptor, DescriptorPool, MessageFactory。

上图中,其关键作用的是prototype = MessageFactory::generated_factory()->GetPrototype(descriptor); assert(prototype == &T::default_instance()); cout << "GetPrototype() = " << prototype << endl; cout << "T::default_instance() = " << &T::default_instance() << endl; cout << endl; T* new_obj = dynamic_cast<T*>(prototype->New()); assert(new_obj != NULL); assert(new_obj != prototype); assert(typeid(*new_obj) == typeid(T::default_instance())); cout << "prototype->New() = " << new_obj << endl; cout << endl; delete new_obj;

根据 type name 自动创建 Message 的关键代码

好了,万事具备,开始行动:

  1. 用 DescriptorPool::generated_pool() 找到一个 DescriptorPool 对象,它包含了程序编译的时候所链接的全部 protobuf Message types
  2. 用 DescriptorPool::FindMessageTypeByName() 根据 type name 查找 Descriptor。
  3. 再用 MessageFactory::generated_factory() 找到 MessageFactory 对象,它能创建程序编译的时候所链接的全部 protobuf Message types。
  4. 然后,用 MessageFactory::GetPrototype() 找到具体 Message Type 的 default instance。
  5. 最后,用 prototype->New() 创建对象。

示例代码见https://github.com/chenshuo/recipes/blob/master/protobuf/codec.h#L69

古之人不余欺也 :-)

注意,createMessage() 返回的是动态创建的对象的指针,调用方有责任释放它,不然就会内存泄露。在 muduo 里,我用 shared_ptr<Message> 来自动管理 Message 对象的生命期。

线程安全性

Google 的文档说,我们用到的那几个 MessageFactory 和 DescriptorPool 都是线程安全的,Message::New() 也是线程安全的。并且它们都是 const member function。

关键问题解决了,那么剩下工作就是设计一种包含长度和消息类型的 protobuf 传输格式

Protobuf 传输格式

陈硕设计了一个简单的格式,包含 protobuf data 和它对应的长度与类型信息,消息的末尾还有一个 check sum。格式如下图,图中方块的宽度是 32-bit。

陈巨大牛关于protobuf的分析

用 C struct 伪代码描述:

设计决策

以下是我在设计这个传输格式时的考虑:

  • signed int。消息中的长度字段只使用了 signed 32-bit int,而没有使用 unsigned int,这是为了移植性,因为 Java 语言没有 unsigned 类型。另外 Protobuf 一般用于打包小于 1M 的数据,unsigned int 也没用。
  • check sum。虽然 TCP 是可靠传输协议,虽然 Ethernet 有 CRC-32 校验,但是网络传输必须要考虑数据损坏的情况,对于关键的网络应用,check sum 是必不可少的。对于 protobuf 这种紧凑的二进制格式而言,肉眼看不出数据有没有问题,需要用 check sum。
  • adler32 算法。我没有选用常见的 CRC-32,而是选用 adler32,因为它计算量小、速度比较快,强度和 CRC-32差不多。另外,zlib 和 java.unit.zip 都直接支持这个算法,不用我们自己实现。
  • type name 以 '\0' 结束。这是为了方便 troubleshooting,比如通过 tcpdump 抓下来的包可以用肉眼很容易看出 type name,而不用根据 nameLen 去一个个数字节。同时,为了方便接收方处理,加入了 nameLen,节省 strlen(),空间换时间。
  • 没有版本号。Protobuf Message 的一个突出优点是用 optional fields 来避免协议的版本号(凡是在 protobuf Message 里放版本号的人都没有理解 protobuf 的设计),让通信双方的程序能各自升级,便于系统演化。如果我设计的这个传输格式又把版本号加进去,那就画蛇添足了。具体请见本人《分布式系统的工程化开发方法》第 57 页:消息格式的选择。

    示例代码

    为了简单起见,采用 std::string 来作为打包的产物,仅为示例。

    打包 encode 的代码:https://github.com/chenshuo/recipes/blob/master/protobuf/codec.h#L35

    解包 decode 的代码:https://github.com/chenshuo/recipes/blob/master/protobuf/codec.h#L99

    测试代码:https://github.com/chenshuo/recipes/blob/master/protobuf/codec_test.cc

    如果以上代码编译通过,但是在运行时出现“cannot open shared object file”错误,一般可以用 sudo ldconfig 解决,前提是 libprotobuf.so 位于 /usr/local/lib,且 /etc/ld.so.conf 列出了这个目录。

    $ make all?# 如果你安装了 boost,可以 make whole

    $ ./codec_test?
    ./codec_test: error while loading shared libraries: libprotobuf.so.6: cannot open shared object file: No such file or directory

    $ sudo ldconfig

    与 muduo 集成

    muduo 网络库将会集成对本文所述传输格式的支持(预计 0.1.9 版本),我会另外写一篇短文介绍 Protobuf Message <=> muduo::net::Buffer 的相互转化,使用 muduo::net::Buffer 来打包比上面 std::string 的代码还简单,它是专门为 non-blocking 网络库设计的 buffer class。

    此外,我们可以写一个 codec 来自动完成转换,就行asio/char/codec.h?那样。这样客户代码直接收到的就是 Message 对象,发送的时候也直接发送 Message 对象,而不需要和 Buffer 对象打交道。

    消息的分发 (dispatching)

    目前我们已经解决了消息的自动创建,在网络编程中,还有一个常见任务是把不同类型的 Message 分发给不同的处理函数,这同样可以借助 Descriptor 来完成。我在 muduo 里实现了 ProtobufDispatcherLite 和 ProtobufDispatcher 两个分发器,用户可以自己注册针对不同消息类型的处理函数。预计将会在 0.1.9 版本发布,您可以先睹为快:

    初级版,用户需要自己做 down casting:https://github.com/chenshuo/recipes/blob/master/protobuf/dispatcher_lite.cc

    高级版,使用模板技巧,节省用户打字:https://github.com/chenshuo/recipes/blob/master/protobuf/dispatcher.cc

    基于 muduo 的 Protobuf RPC?

    Google Protobuf 还支持 RPC,可惜它只提供了一个框架,没有开源网络相关的代码,muduo 正好可以填补这一空白。我目前还没有决定是不是让 muduo 也支持以 protobuf message 为消息格式的 RPC,muduo 还有很多事情要做,我也有很多博客文章打算写,RPC 这件事情以后再说吧。

    注:Remote Procedure Call?(RPC) 有广义和狭义两种意思。狭义的讲,一般特指?ONC RPC,就是用来实现 NFS 的那个东西;广义的讲,“以函数调用之名,行网络通信之实”都可以叫 RPC,比如 Java RMI,.Net Remoting,Apache Thrift,libevent RPC,XML-RPC 等等。

    1 楼 chd_wu 2011-07-20 很不错,学习了

读书人网 >编程

热点推荐