Protobuf初步使用

Protobuf初步使用


简介:
1)介绍了Protobuf的最基本用法;
2)Protobuf版本为3.5.0.1;


相关内容:
1)Protobuf+Cpp的基本用法;
2)使用protobuf时的消息边界问题;


一、Protobuf+Cpp的基本用法
Protobuf可以解析.proto,然后生成消息协议需要的代码,包括.pb.h和.pb.cc。在使用时,搭配上libprotobuf.lib即可。


而对于proto文件的解析有两种,即静态和动态。先说动态。顾名思义,在程序运行的过程中解析proto文件,生成消息结构。换言之,这种方式把.proto文件作为程序的配置文件使用。若有兴趣,可以参考这位的博客


这里主要介绍静态用法。这与需求有关。因为是初次使用,所以更关心它生成的消息结构长什么样,而且暂时没有任意配置消息结构的需求。这位的博客对于这种方式做了很好的介绍。不过他所用的protobuf版本并非Protobuf3。


这里介绍最简洁的用法。
1.1 首先是.proto文件,如下:

syntax = "proto3";      # 指明版本

package my_package;     # 生成cpp的命名空间

message Person {        # 生成cpp的类
    string name = 1;
    int32 id = 2;
}

message AddressBook {   
    repeated Person person = 2;
}


1.2 使用protoc工具生成代码,使用方法如下。然后在output目录下会生成addressbook.pb.h和addressbook.pb.cc。若不加-I选项,在output目录下会新建文件夹,以放置代码。

protoc -I=./demo --cpp_out=./output ./demo/addressbook.proto


1.3 将addressbook.pb.h和addressbook.pb.cc,以及libprotobuf.lib放置到工程目录,即可使用。


1.4 生成的类有AddressBook和Person。
在proto文件中,AddressBook包含的是repeated Person person。所以在使用时,可以是这样:

using namespace my_package;
AddressBook ab;
Person * person = ab.add_person();


然后,对于person的成员的操作,可以是这样:

person->set_name("abc");    // 设置
person->name();     // 获取


1.5 假设只是把protobuf作为数据操作的协议,那到这里就可以使用了。然而,protobuf更多的是作为传输协议,所以还有序列化和反序列化的问题。对于类AddressBook的序列化/反序列化,可以像这样:类和string之间的转换:

string str = ab.SerializeAsString();    // 从类->string

AddressBook cd;
cd.ParseFromString(str);    // 从string->类

或者可以这样:类和文件流之间的转换:

ofstream output("./test", ios::trunc|ios::binary);
ab.SerializeToOstream(&output);     // 从类->文件流
output.close();

ifstream input("./test", ios::binary);
cd.ParseFromIstream(&input);    // 从文件流->类
input.close();


1.6 最后也是最重要的,关于版本校验和内存的释放
主要是protobuf的一个宏定义和一个函数。这里直接展示它们的代码注释。

GOOGLE_PROTOBUF_VERIFY_VERSION:

// 将这个宏放置在main函数中(或任何你想要使用protobuf库的地方之前),
// 以此确认你用protobuf生成的头文件和库的版本是匹配的。
// 如果检测到版本不一致,程序将被中断。
#define GOOGLE_PROTOBUF_VERIFY_VERSION      \
  ::google::protobuf::internal::VerifyVersion( \
    GOOGLE_PROTOBUF_VERSION, GOOGLE_PROTOBUF_MIN_LIBRARY_VERSION,         \
    __FILE__)

google::protobuf::ShutdownProtobufLibrary();

// 关闭protobuf库,释放由库或者*.pb.cc分配的内存。

// 两种情况下,你会需要调用这个函数:
// 1. 有严格的内存管理要求,即每个malloc都要对应一个free,即使是对于那些要一直存活到进程终止的对象;
// 2. 所编写的程序是一个动态加载库,且在退出时需要自行清理资源;

// 这个函数可以重复多次调用。但是,在调用了这个函数之后,再去使用protobuf库的其它部分,是很不安全的。
LIBPROTOBUF_EXPORT void ShutdownProtobufLibrary();



二、使用protobuf时的消息边界问题
protobuf似乎是将消息边界的处理留给了我们自己。这在UDP中使用起来倒是问题不大,而在TCP时则需要考虑边界的问题,比如说一次收到了两个AddressBook对象的数据。
可以尝试一个例子:先将一个类序列化为string,然后字符串x2之后,再反序列化为类,像这样:

AddressBook ab;
ab.add_person();
cout << ab.person().size() << endl;  // 输出1
string str = ab.SerializeAsString();

str += str;
AddressBook cd;
cd.ParseFromString(str);
cout << cd.person().size() << endl;     // 输出2


所以,在使用时,可以在数据前后加上数据头和尾,比如像这样:

struct Head;  // 可以包含数据长度,版本等信息
struct Tail;  // 可以包含校验和,终止标识等信息

struct Msg
{
    Head head;  
    string data;  // protobuf的对象序列化后的结果
    Tail tail;

    string Serialize()
    {
        string buf = string((char*)&head, sizeof(head));
        buf += data;
        buf += string((char*)&tail, sizeof(tail));
        return buf;
    }

    void Deserialize(string buf)
    {
        /*还需要最小长度校验之类,这里先略去*/

        head = *(Head*)buf.c_str();  // 取消息头
        int size = head.size;  // 消息体长度
        data = buf.substr(sizeof(Head), size);  // 取消息体
        tail = *(Tail*)(buf.c_str()+sizeof(Head)+size);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值