protobuf3的简单使用

第1章 定义.proto 文件

首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Message。下面是一个proto文件的例子。

syntax = "proto3";

package tutorial;

message Person {
required string name = 1;
required int32 id = 2;        // Unique ID number for this person.
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;
 
}
 
 
// Our address book file is just one of these.
 
message AddressBook {
repeated Person person = 1;
 
}

一个proto文件主要包含package定义、message定义和字段定义三个部分,还有一些可选项。

1.1 定义package

package在c++中对应namespace。

1.2 定义message

Message在C++中对应class。Message中定义的全部属性在class中全部为private的。

Message的嵌套使用可以嵌套定义,也可以采用先定义再使用的方式。

向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式

1.3 定义字段

属性定义分为四部分:修饰符+类型+属性名+唯一标识符+[默认值],其示意如下所示:

required string	name = 1 [default=""];

第2章 编译 .proto 文件

可以通过定义好的.proto文件来生成C++代码,需要基于.proto文件运行protocol buffer编译器protoc。如果你没有安装编译器,下载安装包并遵照README安装。运行的命令如下所示:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR 
  • MPORT_PATH声明了一个.proto文件所在的具体目录。如果忽略该值,则使用当前目录。如果有多个目录则可以 对–proto_path 写多次,它们将会顺序的被访问并执行导入。-I=IMPORT_PATH是它的简化形式。
  • cpp_out 在目标目录DST_DIR中产生C++代码,可以在C++代码生成参考中查看更多。如果DST_DIR以.zip或者.jar结尾,编译器会将输出写到一个ZIP格式文件或者符合JAR标准的.jar文件中。注意如果输出已经存在则会被覆盖,编译器还没有智能到可以追加文件。
  • 必须使用一个或多个.proto文件作为输入,多个.proto文件可以只指定一次。虽然文件路径是相对于当前目录的,每个文件必须位于其IMPORT_PATH下,以便每个文件可以确定其规范的名称。

第3章 使用message

3.1 类成员变量的访问

编译生成的.h文件中定义了类成员的访问方法(API)。以下举例对于不同类型的成员变量的常用API:

  • 获取成员变量值直接采用使用成员变量名(全部为小写),设置成员变量值,使用在成员变量名前加set_的方法。

  • 对于普通成员变量提供has_方法判断变量值是否被设置;提供clear_方法清除设置的变量值。

  • 对于string类型,提供多种set_方法,其参数不同。同时,提供了一个mutable_方法,返回变量值的可修改指针。

  • 对于嵌套类型,也提供了一个mutable_方法,返回变量值的可修改指针。

  • 对于repeated变量,提供了其它一些特殊的方法:

    • size_方法:返回repeated field’s
    • 通过下脚标访问其中的数组成员组
    • 通过下脚标返回其中的成员的mutable_的方法
    • add_方法:增加一个成员。

3.2 标准message方法

生成的.h文件中的class都继承自::google::protobuf::Message类,Message类提供了一些方法可以检查或者操作整个message,如下所示:

bool IsInitialized() const;检查是否所有required变量都已经初始化;
string DebugString() const;返回message的可阅读的表示,主要用于调试程序;
void CopyFrom(const Person& from);使用一个message的值覆盖本message;
void Clear();清空message的所有成员变量值。

3.3 编码和解码函数

每个message类都提供了写入和读取message数据的方法,发送接收端所使用的加码解码方法不一定非得配对,即发送端用SerializeToString 接收端不一定非得用ParseFromString ,可以使用其他解码方法,编码和解码函数如下所示:

bool SerializeToString(string* output) const;把message编码进output。
bool ParseFromString(const string& data);从string解码到message
bool SerializeToArray(char* buf,int size) const;把message编码进数组buf.
bool ParseFromArray(const char* buf,int size);把buf解码到message。此解码方法效率较ParseFromString高很多,所以一般用这种方法解码。
bool SerializeToOstream(ostream* output) const;把message编码进ostream
bool ParseFromIstream(istream* input);从istream解码到message

第4章 使用示例

4.1 简单message生成的C++代码

这里先定义一个最简单的message:

message LogonReqMessage {
    required int64 acctID = 1;
    required string passwd = 2;
}

下面我们来看一下由message LogonReqMessage生成的C++类的部分声明,以及常用方法的说明性注释:

class LogonReqMessage : public ::google::protobuf::MessageLite {
 
    public:
        LogonReqMessage();
        virtual ~LogonReqMessage();
 
 
        // implements Message ----------------------------------------------
        //下面的成员函数均实现自MessageLite中的虚函数。
        //创建一个新的LogonReqMessage对象,等同于clone。
        LogonReqMessage* New() const;
        //用另外一个LogonReqMessage对象初始化当前对象,等同于赋值操作符重载(operator=)
        void CopyFrom(const LogonReqMessage& from);
        //清空当前对象中的所有数据,既将所有成员变量置为未初始化状态。
        void Clear();
        //判断当前状态是否已经初始化。
        bool IsInitialized() const;
        //在给当前对象的所有变量赋值之后,获取该对象序列化后所需要的字节数。
        int ByteSize() const;
        //获取当前对象的类型名称。
        inline ::std::string GetTypeName() const;
        


        // required int64 acctID = 1;
        //下面的成员函数都是因message中定义的acctID字段而生成。
        //这个静态成员表示AcctID的标签值。命名规则是k + FieldName(驼峰规则) + FieldNumber。
        static const int kAcctIDFieldNumber = 1;
        //如果acctID字段已经被设置返回true,否则false。
        inline bool has_acctid() const;
        //执行该函数后has_acctid函数将返回false,而下面的acctid函数则返回acctID的缺省值。
        inline void clear_acctid();
        //返回acctid字段的当前值,如果没有设置则返回int64类型的缺省值。
        inline ::google::protobuf::int64 acctid() const;
        //为acctid字段设置新值,调用该函数后has_acctid函数将返回true。
        inline void set_acctid(::google::protobuf::int64 value);
 
   
 
        // required string passwd = 2;
        //下面的成员函数都是因message中定义的passwd字段而生成。这里生成的函数和上面acctid
        //生成的那组函数基本相似。因此这里只是列出差异部分。
        static const int kPasswdFieldNumber = 2;
        inline bool has_passwd() const;
        inline void clear_passwd();
        inline const ::std::string& passwd() const;
        inline void set_passwd(const ::std::string& value);
        //对于字符串类型字段设置const char*类型的变量值。
        inline void set_passwd(const char* value);
        inline void set_passwd(const char* value, size_t size);
        //可以通过返回值直接给passwd对象赋值。在调用该函数之后has_passwd将返回true。
        inline ::std::string* mutable_passwd();
        //释放当前对象对passwd字段的所有权,同时返回passwd字段对象指针。调用此函数之后,passwd字段对象的所有权将移交给调用者。此后再调用has_passwd函数时将返回false。
        inline ::std::string* release_passwd();
 
    private:
 
        //... ...
 
    };

下面是读写LogonReqMessage对象的C++测试代码和说明性注释:

void testSimpleMessage()
 
    {
 
        printf("==================This is simple message.================\n");
        //序列化LogonReqMessage对象到指定的内存区域。
        LogonReqMessage logonReq;
        logonReq.set_acctid(20);
        logonReq.set_passwd("Hello World");
        //提前获取对象序列化所占用的空间并进行一次性分配,从而避免多次分配
        //而造成的性能开销。通过该种方式,还可以将序列化后的数据进行加密。
        //之后再进行持久化,或是发送到远端。
        int length = logonReq.ByteSize();
        char* buf = new char[length];
        logonReq.SerializeToArray(buf,length);
        //从内存中读取并反序列化LogonReqMessage对象,同时将结果打印出来。
        LogonReqMessage logonReq2;
        logonReq2.ParseFromArray(buf,length);
        printf("acctID = %I64d, password = %s\n",logonReq2.acctid(),logonReq2.passwd().c_str());
 
        delete [] buf;
 
    }

4.2 嵌套message生成的C++代码

这里定义一个嵌套的message:

enum UserStatus {

          OFFLINE = 0;

          ONLINE = 1;

      }

      enum LoginResult {

          LOGON_RESULT_SUCCESS = 0;

          LOGON_RESULT_NOTEXIST = 1;

          LOGON_RESULT_ERROR_PASSWD = 2;

          LOGON_RESULT_ALREADY_LOGON = 3;

          LOGON_RESULT_SERVER_ERROR = 4;

      }

      message UserInfo {

          required int64 acctID = 1;

          required string name = 2;

          required UserStatus status = 3;

      }

      message LogonRespMessage {

          required LoginResult logonResult = 1;

          required UserInfo userInfo = 2; //这里嵌套了UserInfo消息。

      }

对于上述消息生成的C++代码,UserInfo因为只是包含了原始类型字段,因此和上例中的LogonReqMessage没有太多的差别,这里也就不在重复列出了。由于LogonRespMessage消息中嵌套了UserInfo类型的字段,在这里我们将仅仅给出该消息生成的C++代码和关键性注释:

class LogonRespMessage : public ::google::protobuf::MessageLite {

    public:

        LogonRespMessage();

        virtual ~LogonRespMessage();
 
        // implements Message 

        //这部分函数和之前的例子一样。  

        // required .LoginResult logonResult = 1;

        //下面的成员函数都是因message中定义的logonResult字段而生成。
		//这一点和前面的例子基本相同,只是类型换做了枚举类型LoginResult。   

        static const int kLogonResultFieldNumber = 1;

        inline bool has_logonresult() const;

        inline void clear_logonresult();

        inline LoginResult logonresult() const;

        inline void set_logonresult(LoginResult value);  

        // required .UserInfo userInfo = 2;

        //下面的成员函数都是因message中定义的UserInfo字段而生成。
		//这里只是列出和非消息类型字段差异的部分。

        static const int kUserInfoFieldNumber = 2;

        inline bool has_userinfo() const;

        inline void clear_userinfo();

        inline const ::UserInfo& userinfo() const;

        //可以看到该类并没有生成用于设置和修改userInfo字段set_userinfo函数,而是将该工作交给了下面的mutable_userinfo函数。因此每当调用函数之后,Protocol Buffer都会认为该字段的值已经被设置了,同时has_userinfo函数亦将返回true。在实际编码中,我们可以通过该函数返回userInfo字段的内部指针,并基于该指针完成userInfo成员变量的初始化工作。

        inline ::UserInfo* mutable_userinfo();

        inline ::UserInfo* release_userinfo();

    private:

        //... ...

    };

下面是读写LogonRespMessage对象的C++测试代码和说明性注释:

void testNestedMessage()

    {

        printf("==================This is nested message.================\n");

        LogonRespMessage logonResp;

        logonResp.set_logonresult(LOGON_RESULT_SUCCESS);

        //如上所述,通过mutable_userinfo函数返回userInfo字段的指针,之后再初始化该对象指针。

        UserInfo* userInfo = logonResp.mutable_userinfo();

        userInfo->set_acctid(200);

        userInfo->set_name("Tester");

        userInfo->set_status(OFFLINE);

        int length = logonResp.ByteSize();

        char* buf = new char[length];

        logonResp.SerializeToArray(buf,length);

        LogonRespMessage logonResp2;

        logonResp2.ParseFromArray(buf,length);

        printf("LogonResult = %d, UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n",logonResp2.logonresult(),logonResp2.userinfo().acctid(),logonResp2.userinfo().name().c_str(),logonResp2.userinfo().status());

        delete [] buf;

    }

4.3 repeated嵌套message生成的C++代码

这里定义一个包含repeated字段的嵌套message:

message BuddyInfo {

          required UserInfo userInfo = 1;

          required int32 groupID = 2;

      }

      message RetrieveBuddiesResp {

          required int32 buddiesCnt = 1;

          repeated BuddyInfo buddiesInfo = 2;

      }

对于上述消息生成的代码,我们将只是针对RetrieveBuddiesResp消息所对应的C++代码进行详细说明,其余部分和前面小节的例子基本相同,可直接参照。而对于RetrieveBuddiesResp类中的代码,我们也仅仅是对buddiesInfo字段生成的代码进行更为详细的解释:

class RetrieveBuddiesResp : public ::google::protobuf::MessageLite {

    public:

        RetrieveBuddiesResp();

        virtual ~RetrieveBuddiesResp();

        //其余代码的功能性注释均可参照前面的例子。
    
        // repeated .BuddyInfo buddiesInfo = 2;

        static const int kBuddiesInfoFieldNumber = 2;

        //返回数组中成员的数量。

        inline int buddiesinfo_size() const;

        //清空数组中的所有已初始化成员,调用该函数后,buddiesinfo_size函数将返回0。

        inline void clear_buddiesinfo();

        //返回数组中指定下标所包含元素的引用。

        inline const ::BuddyInfo& buddiesinfo(int index) const;

        //返回数组中指定下标所包含元素的指针,通过该方式可直接修改元素的值信息。

        inline ::BuddyInfo* mutable_buddiesinfo(int index);

        //像数组中添加一个新元素。返回值即为新增的元素,可直接对其进行初始化。

        inline ::BuddyInfo* add_buddiesinfo();

        //获取buddiesInfo字段所表示的容器,该函数返回的容器仅用于遍历并读取,不能直接修改。

        inline const ::google::protobuf::RepeatedPtrField< ::BuddyInfo >& buddiesinfo() const;

        //获取buddiesInfo字段所表示的容器指针,该函数返回的容器指针可用于遍历和直接修改。

        inline ::google::protobuf::RepeatedPtrField< ::BuddyInfo >*

          mutable_buddiesinfo();

    private:

        //... ...

    };

下面是读写RetrieveBuddiesResp对象的C++测试代码和说明性注释:

void testRepeatedMessage()

    {

        printf("==================This is repeated message.================\n");

        RetrieveBuddiesResp retrieveResp;

        retrieveResp.set_buddiescnt(2);

        BuddyInfo* buddyInfo = retrieveResp.add_buddiesinfo();

        buddyInfo->set_groupid(20);

        UserInfo* userInfo = buddyInfo->mutable_userinfo();

        userInfo->set_acctid(200);

        userInfo->set_name("user1");

        userInfo->set_status(OFFLINE);

        buddyInfo = retrieveResp.add_buddiesinfo();

        buddyInfo->set_groupid(21);

        userInfo = buddyInfo->mutable_userinfo();

        userInfo->set_acctid(201);

        userInfo->set_name("user2");

        userInfo->set_status(ONLINE);

        int length = retrieveResp.ByteSize();

        char* buf = new char[length];

        retrieveResp.SerializeToArray(buf,length);

        RetrieveBuddiesResp retrieveResp2;

        retrieveResp2.ParseFromArray(buf,length);

        printf("BuddiesCount = %d\n",retrieveResp2.buddiescnt());

        printf("Repeated Size = %d\n",retrieveResp2.buddiesinfo_size());

        //这里仅提供了通过容器迭代器的方式遍历数组元素的测试代码。事实上,通过buddiesinfo_size和buddiesinfo函数亦可循环遍历。

        RepeatedPtrField<BuddyInfo>* buddiesInfo = retrieveResp2.mutable_buddiesinfo();

        RepeatedPtrField<BuddyInfo>::iterator it = buddiesInfo->begin();

        for (; it != buddiesInfo->end(); ++it) {

            printf("BuddyInfo->groupID = %d\n", it->groupid());

            printf("UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %d\n", it->userinfo().acctid(), it->userinfo().name().c_str(),it->userinfo().status());

        }

        delete [] buf;

    }
C++ protobuf3 是 Google 开发的一种高效的序列化框架,可以将结构化数据序列化为二进制格式,以便于在网络传输或磁盘存储中使用。它的主要特点是高效、可扩展、跨平台,并且支持多种语言的互操作性。使用 protobuf3,可以轻松地在不同的编程语言和操作系统之间传递数据。 在使用 protobuf3 时,需要先定义一个 .proto 文件来描述数据结构和消息格式。然后使用 protobuf 编译器将 .proto 文件编译成对应编程语言的源代码文件,例如 C++。在 C++ 中,可以使用 protobuf3 库提供的 API 来进行序列化和反序列化操作,从而实现数据的传输和存储。 以下是一个简单的示例,展示如何使用 protobuf3C++ 中进行序列化和反序列化操作: ``` // 定义 .proto 文件 message MyMessage { int32 id = 1; string name = 2; } // 使用 protobuf 编译器生成 C++ 代码 protoc --cpp_out=. my_message.proto // 在 C++使用 protobuf3 序列化和反序列化数据 MyMessage message; message.set_id(1); message.set_name("Hello World!"); // 序列化为二进制格式 std::string serialized_data; message.SerializeToString(&serialized_data); // 反序列化为原始数据 MyMessage new_message; new_message.ParseFromString(serialized_data); ``` 以上代码演示了如何使用 protobuf3C++ 中进行序列化和反序列化操作。可以看出,使用 protobuf3 可以方便地实现数据的传输和存储,同时也提高了性能和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值