protobuffer学习总结

博客搬家,原地址:

protobuffer是google开发的一种数据描述语言,它能够将结构化的数据序列化,并切可以将序列化的数据进行反序列化恢复原有的数据结构。一般用于数据存储以及通信协议方面。

如果是第一次使用protobuffer,我们可以将其与json或者xml进行类比,其实它与json或xml类似都可以作为数据的存储方式,不同的是json和xml是文本格式,而protobuffer是二进制格式。二进制格式不利于使用者直观的阅读,但是与json以及xml相比它有更多的优点。

protoBuffer相比于xml的优点

  • 更加简介
  • 体积小:消息大小只需要xml的1/10~1/3
  • 解析速度快:解析速度比xml快20~100倍
  • 使用proto Buffer的编译器,可以生成方便在编程中使用的数据访问代码.
  • 具有更好的兼容性,很好的支持向上或向下兼容的特性
  • 提供多种序列化的出口和入口,如文件流,string流,array流等等

protobuffer语法

消息类型实例:

Package example;

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

  enum PhoneType{
    mobile = 1;
    home = 2;
    work = 3;
  }

  message PhoneNumber{
    required string number = 1;
    optional PhoneType type = 2;
  }

  repeated PhoneNumber phone = 4;
}

指定字段规则

protobuffer中字段规则包括一下三种:

  • required:实例中必须包含的字段
  • optional:实例中可以选择性包含的字段,若实例没有指定,则为默认值,若没有设置该字段的默认值,其值是该类型的默认值。如string默认值为"",bool默认值为false,整数默认值为0。
  • repeated: 可以有多个值的字段,这类变量类似于vector,可以存储此类型的多个值。

由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。
一般情况下慎重使用required字段,当此字段一定是必要的时候才使用。

repeated使用实例:

message Person {  
  required int32 age = 1;  
  required string name = 2;  
}  

message Family {  
  repeated Person person = 1;  
}
int main(int argc, char* argv[])  
{  

    GOOGLE_PROTOBUF_VERIFY_VERSION;  

    Family family;  
    Person* person;  

    // 添加一个家庭成员,John  
    person = family.add_person();  
    person->set_age(25);  
    person->set_name("John");  

    // 添加一个家庭成员,Lucy  
    person = family.add_person();  
    person->set_age(23);  
    person->set_name("Lucy");  

    // 添加一个家庭成员,Tony  
    person = family.add_person();  
    person->set_age(2);  
    person->set_name("Tony");  

    // 显示所有家庭成员  
    int size = family.person_size();  

    cout << "这个家庭有 " << size << " 个成员,如下:" << endl;  

    for(int i=0; i<size; i++)  
    {  
        Person psn = family.person(i);  
        cout << i+1 << ". " << psn.name() << ", 年龄 " << psn.age() << endl;  
    }  

    getchar();  
    return 0;  
}  

数据类型

protobuffer中的数据类型与C++数据类型之间的关联如下图:

protobuffer类型C++类型
doubledouble
floatfloat
int32int32
int64int64
uint32uint32
uint64uint64
sint32int32
sint64int64
fixed32uint32
fixed64uint64
sfixed32uint32
sfixed64uint64
boolbool
stringstring
bytesstring

枚举

当需要定义一个消息类型的时候,我们可能想为某一个字段指定预定义列表中的值。这个时候就需要用到枚举

如:

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3 [default = 10];
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  optional Corpus corpus = 4 [default = UNIVERSAL];
}

变量标识号

在proto数据结构中,每一个变量都有唯一的数字标识。这些标识符的作用是在二进制格式中识别各个字段的,一旦开始使用就不可再改变。

此处需要注意的是1-15之内的标号在存储的时候只占一个字节,而大于15到162047之间的需要占两个字符,所以我们尽量为频繁使用的字段分配1-15内的标识号
。另外19000-19999之内的标识号已经被预留,不可用。最大标识号为2^29-1。

嵌套

protobuffer中的消息可以嵌套消息,也就是在一个message中定义另一个message。如上面实例可以看出。

扩展

我们可以通过扩展对proto文件进行扩展,而不需要直接区编辑原文件。

例如有原文件:

message Foo{
  //...
  extensions 100 to 199;
}

上述extensions 100 to 199表示此范围内的标识号被保留为扩展用。我们在扩展文件中就可以使用这些标识号了。

extend Foo{
  optional int32 bar = 126;
}

上述为扩展。当用户的Foo消息被编码的时候,数据的传输格式与用户在Foo里定义新字段的效果是完全一样的。然而,要在程序代码中访问扩展字段的方法与访问普通的字段稍有不同——生成的数据访问代码为扩展准备了特殊的访问函数来访问它。例如,下面是如何在C++中设置bar的值:

Foo foo;
foo.SetExtentions(bar, 15);

注释

与c++注释风格相同。双斜杠

向上且向下兼容更新消息

当在需求不断增加的过程中,数据结构也会不断变化,这个时候就需要我们去更新消息。怎么才能做到更新消息不会影响之前的数据和代码。这个时候我们更新消息需要遵循以下几个原则:

  • 不要更改任何已有的字段的数值标识
  • 所添加的字段必须是optional或者repeated。

包名称解析

为了防止消息明明冲突,我们往往会在文件的开始出生命包,包的作用相当于命名空间。在编译成C++代码时也是namespace。例如:

package foo.bar;
message open{
  ///...
}

在C++对open进行访问的时候的访问方式为:

foo::bar::open test;

C++程序使用protobuffer

按照上面的规则我们可以设计出合理的protobuffer类型。然后下一步就是将proto文件生成C++头文件和实现文件,将.proto文件编译成C接口的方法如下:

protoc -I=SOURCE_DIR --cpp_out=DIST_DIR test.proto

使用proto生成的头文件进行编译时需要链接protobuffer库。具体为:

g++ main.cpp test.pb.cc -lprotobuf

protobuffer编译为C++代码的常用接口

对于C++来说,编译器会为每个.proto文件生成一个.h文件和.cc文件。.proto文件中的每一个消息对应一个类。
protobuffer中常用的函数:

  • has_name() :判断是否有当前成员
  • clear_name() :清空该成员变量值
  • name() :获取成员的变量值
  • set_name(string) :设置变量值
  • set_name(const char*):设置变量值
  • set_name(int) :设置变量值
  • clear() :清空所有元素为空状态
  • void CopyFrom(person):从给定的对象复制。
  • mutable_name() :获取变量name的指针
  • add_name() :为repeated变量增加值
  • ByteSize() :获取变量所占的字节数
    若有元素data属性为repeated,其行为类似于vector,则此时则可用下列函数:
  • add_data() : 添加data元素,返回值为Date*类型。
  • data_size() : 获取repeated元素size,即元素的个数。
  • data(i) : 获取data中地i个元素。
  • ByteSize() : 获取序列化之后的protobuff对象的长度。
  • CopyFrom(const ProtoType&): 从一个protobuf对象拷贝到另一个

常用的序列化方法

C数组的序列化与反序列化的API

如果想将其序列为char*并通过socket进行传输,这是使用SerializeToArray来达到目的。
除了下述的SerializeToArray方法之外,还有方法SerializePartialToArray,两者用法相同,其中唯一的区别在于SerializePartialToArray允许忽略required字段,而前者不允许

void* parray = (char*)malloc(256);
//API
bool ParseFromArray(const void* data, int size);
bool SerializeToArray(void* data. int size);

void set_people()               
{  
    wp.set_name("sealyao");     
    wp.set_id(123456);          
    wp.set_email("sealyaog@gmail.com");  
    wp.SerializeToArray(parray,256);  
}  

void get_people()               
{  
    rap.ParseFromArray(parray,256);  
    cout << "Get People from Array:" << endl;  
    cout << "\t Name : " <<rap.name() << endl;  
    cout << "\t Id : " << rap.id() << endl;  
    cout << "\t email : " << rap.email() << endl;  
}  

C++ String的序列化与反序列化API

除了下述的SerializeToString方法之外,还有方法SerializePartialToString,两者用法相同,其中唯一的区别在于SerializePartialToString允许忽略required字段,而前者不允许

//C++string序列化和序列化API  
bool SerializeToString(string* output) const;  
bool ParseFromString(const string& data);  
//使用:  
void set_people()               
{  
    wp.set_name("sealyao");     
    wp.set_id(123456);          
    wp.set_email("sealyaog@gmail.com");  
    wp.SerializeToString(&pstring);  
}  

void get_people()               
{  
    rsp.ParseFromString(pstring);    
    cout << "Get People from String:" << endl;  
    cout << "\t Name : " <<rsp.name() << endl;  
    cout << "\t Id : " << rsp.id() << endl;  
    cout << "\t email : " << rsp.email() << endl;  
}  

文件描述符序列化与反序列化API

//文件描述符的序列化和序列化API  
bool SerializeToFileDescriptor(int file_descriptor) const;  
bool ParseFromFileDescriptor(int file_descriptor);  

//使用:  
void set_people()  
{  
   fd = open(path,O_CREAT|O_TRUNC|O_RDWR,0644);  
   if(fd <= 0){  
       perror("open");  
       exit(0);   
   }     
   wp.set_name("sealyaog");  
   wp.set_id(123456);  
   wp.set_email("sealyaog@gmail.com");  
   wp.SerializeToFileDescriptor(fd);     
   close(fd);  
}  

void get_people()  
{  
   fd = open(path,O_RDONLY);  
   if(fd <= 0){  
       perror("open");  
       exit(0);  
   }  
   rp.ParseFromFileDescriptor(fd);  
   std::cout << "Get People from FD:" << endl;  
   std::cout << "\t Name : " <<rp.name() << endl;  
   std::cout << "\t Id : " << rp.id() << endl;  
   std::cout << "\t email : " << rp.email() << endl;  
   close(fd);  
}  

C++ stream 序列化和反序列化API

//C++ stream 序列化/反序列化API  
bool SerializeToOstream(ostream* output) const;  
bool ParseFromIstream(istream* input);  

//使用:  
void set_people()  
{  
    fstream fs(path,ios::out|ios::trunc|ios::binary);  
    wp.set_name("sealyaog");  
    wp.set_id(123456);  
    wp.set_email("sealyaog@gmail.com");  
    wp.SerializeToOstream(&fs);      
    fs.close();  
    fs.clear();  
}  

void get_people()  
{  
    fstream fs(path,ios::in|ios::binary);  
    rp.ParseFromIstream(&fs);  
    std::cout << "\t Name : " <<rp.name() << endl;  
    std::cout << "\t Id : " << rp.id() << endl;   
    std::cout << "\t email : " << rp.email() << endl;     
    fs.close();  
    fs.clear();  
}  

参考链接:

http://blog.csdn.net/mycwq/article/details/19622571
http://colobu.com/2015/01/07/Protobuf-language-guide/
https://worktile.com/tech/share/prototol-buffers
http://tech.meituan.com/serialization_vs_deserialization.html
http://blog.csdn.net/weiwangchao_/article/details/16797763

  • 10
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Protobuf是Google开发的一种二进制数据交换格式,其主要用于序列化结构化数据,使其能够在网络中进行传输。Protobuf定义了一种结构化数据格式,通过这种格式可以定义数据的类型和字段,然后通过编译器生成对应的代码,用于在不同的编程语言中进行数据序列化和反序列化操作。 WebSocket是一种在单个TCP连接上进行全双工通信的协议,它提供了一种实时的、持久的、双向的通信通道,可以在客户端和服务器之间进行双向通信。与HTTP不同,WebSocket不需要通过不断的请求和响应来实现数据的交换,而是通过使用WebSocket协议,可以直接在连接建立后进行数据的传输和接收。 在前端与服务端进行通信时,可以使用Protobuf与WebSocket结合使用。通过在服务端和前端分别定义相同的数据结构,并使用Protobuf进行序列化和反序列化操作,可以将数据以二进制形式传输,并节省带宽和提高传输效率。在WebSocket连接建立后,可以直接通过发送和接收二进制数据来进行双向通信,而不需要通过HTTP请求和响应的方式。 通过使用Protobuf与WebSocket结合,可以在前端与服务端之间建立实时的、持久的、双向的通信通道,实现高效的数据交换和迅速的响应。同时,使用Protobuf进行数据序列化和反序列化,可以保证数据的一致性和准确性,提高数据交换的可靠性。因此,Protobuf与WebSocket结合可以在Web开发中发挥重要的作用,提升前端与服务端之间的通信效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值