您的位置:首页 > 其它

protobuf简介与开发指导

2014-02-23 19:08 351 查看



Protocol Buffers是谷歌开发的数据交换格式,Google使用它作为内部几乎所有的RPC协议和文件格式。ProtocolBuffers是独立于编程语言,独立于系统平台的高效可扩展结构化数据序列化方法,不仅可以用于通信协议,而且可以作为数据存储格式,目前支持JAVA,C++,Python等。
Protocol Buffers 类似于XML,JSON,但更小,更快,更简单,它支持自动化,只需定义一次数据结构的格式,然后就可以生成各种你所需要的语言的代码,从数据流中读写数据。而且你可以随时扩展新的字段,而不需要担心兼容以前的代码,它会忽略新的字段。使用它首先要创建一个.protp格式文件,里面定义所需的数据结构消息,每一个消息都包含一系列的名字-类型对。例如:

message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK= 2;
}

message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default =HOME];
}

repeated PhoneNumber phone = 4;
}

从上面可以看到,每一个消息都包含至少一个唯一编号的成员,每个成员都有类型和名字,成员类型可以是整数,浮点数,boolean 类型,字符串,或者其他protobuf消息类型。你可以定义可选成员,必选成员(如果需要以后升级扩展,慎重选择,因为如果后来的程序去掉必选成员会产生问题),和重复成员。成员编号在1-15之间效率比大于15的高,所以可以把常用成员编号为15以内,其他在15以上。关于数据类型的更多信息访问https://developers.google.com/protocol-buffers/docs/proto
一旦定义好proto文件,使用ProtocolBuffer编译器protoc 生成所需的类代码,这些代码可以序列化和反序列化数据,获取/设置每个成员。(下面网址有编译好的windows二进制程序https://code.google.com/p/protobuf/downloads/list)编译文件需要制定源文件和目的目录,例如生成C++代码:

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


执行后生成两个文件addressbook.pb.h和addressbook.pb.cc,分别为头文件和实现文件。我们来看看它的头文件:

// name

inline boolhas_name() const;

inline voidclear_name();

inline const::std::string& name() const;

inline voidset_name(const ::std::string& value);

inline voidset_name(const char* value);

inline ::std::string*mutable_name();

// id

inline boolhas_id() const;

inline voidclear_id();

inlineint32_t id() const;

inline voidset_id(int32_t value);

// email

inline boolhas_email() const;

inline voidclear_email();

inline const::std::string& email() const;

inline voidset_email(const ::std::string& value);

inline voidset_email(const char* value);

inline ::std::string*mutable_email();

// phone

inline intphone_size() const;

inline voidclear_phone();

inline const::google::protobuf::RepeatedPtrField<
::tutorial::Person_PhoneNumber>& phone() const;

inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber>*
mutable_phone();

inline const::tutorial::Person_PhoneNumber& phone(int index) const;

inline ::tutorial::Person_PhoneNumber*mutable_phone(int index);

inline ::tutorial::Person_PhoneNumber*add_phone();

每个成员都有获取和设置函数,并且函数名是依据proto文件定义的名字结尾的。重复成员还有_size()函数,获取重复个数,非重复成员有has_函数,判断是否有该字段。使用这些数据非常方便,例如下面C++的代码:

Person person;
person.set_name("JohnDoe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstreamoutput("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

// Then, later on,you could read your message back in:
fstreaminput("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout <<"Name: " << person.name() << endl;
cout <<"E-mail: " << person.email() << endl;

优化建议:
1. 如果有大量相同消息需要解析,可以重用同一个变量,避免重复申请释放内存。
2. 使用new/delete经常申请释放小内存,效率较低,推荐使用谷歌的gperftools,特别是在多线程环境,目前实际项目中,单线程的程序很少见了。
从上面的例子可以看出protobuf这样的库是很方便高效的,那么自然的想到在网络编程中用来做业务消息的序列化、反序列化支持。在基于UDP协议的网络应用中,由于UDP本身是有边界,那么用protobuf来处理业务消息就很方便。但在TCP应用中,由于TCP协议没有消息边界,这就需要有一种机制来确定业务消息边界。在TCP网络编程中这是必须面对的问题。

注意上面的address_book.ParseFromIstream调用,如果流参数的内容多一个字节或者少一个字节,该方法都会返回失败(虽然某些字段可能正确得到结果了),也就是说送给反序列化的数据参数除了格式正确还必须有正确的大小。因此在tcp网络编程中,要反序列化业务消息,就要先知道业务数据的大小。而且在实际应用中可能在一个发送操作中,发送多个业务消息,而且每个业务消息的大小、类型都不一样。而且可能发送很大的数据流,比如文件,一般可以在每个消息前面加个固定长度的包长度字段来实现。

在官方文档中也提到,protobuf并不太适合来作大数据的处理,当业务消息超过1M时,就应该考虑是否应该用另外的替代方案。当然对于大数据,你也可以分割为多个小块用protobuf做小块消息封装进行传递。但对很多应用这样的作法显得比较多余,比如发送一个大的文件,一般是在接收方从协议栈收到多少数据就写多少数据到磁盘,这是一种边接收边处理的流模式,这种模式基本上和每次收到的数据量没有关系。这种模式下再采用分割成小消息进行反序列化就显得多此一举了。

参考文献: https://developers.google.com/protocol-buffers/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: