最近在读别人代码的时候发现一个的东西,名字叫protobuf, 感觉挺好用的,写在这里,留个记录。那么什么是protobuf 呢?假如您在网上搜索,应该会得到类似这样的文字介绍:
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
说的明白点,它其实是一个可以帮你生成自定义数据结构的代码,并提供了序列化该结构的方法,并且支持多语言,跨平台等一系列优点。多说无益,直接说说该怎么用它。
首先 你需要编写proto文件,在这个文件中 用简单的方法定义了你想要定义的数据结构的组成信息。比如定义一个helloworld的信息,里面包含name, password, email 这三个信息。那么你的proto文件应该是这个样子的:
package im;
message helloworld
{
required string usrname = 1;
required string passwd = 2;
optional string email = 3;
}
im 说明了包的名称, helloworld 说明了 具体的结构类型。 一个比较好的习惯是认真对待 proto 文件的文件名。比如将命名规则定于如下:packageName.MessageName.proto
接下来 你需要运行一个命令来来生成该数据结构的代码
假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/im.helloworld.proto
那么这个时候你就会在DST_DIR下面看到im.helloworld.pb.h im.helloworld.pb.cpp
此时基本就大功告成了,你就可以在你的代码里使用关于helloworld这个数据结构的一切了。
这个工具主要比较方便的地方是它给你提供的序列化工具。可以直接将一个数据结构序列化为字符、或者从字符反序列化为数据结构。你想,这就为很多的网络程序提供了方便,在客户机在向服务端传递消息的时候可以直接将需要传递的消息用一个数据结构封装,然后用protobuf提供的序列化方法,序列化为一个字符串,然后你人为的加一个包头(包含消息的长度和消息的类型)做一次encode;在服务端接收该消息的时候根据encode的方法对包进行接收,接收完之后再用protobuf提供的反序列化方法,从字符串再次还原为具体的数据结构信息。整个过程就不需要你去造*去做那些繁琐的字符解析拼装的工作了,是不是很爽? 这个我认为是protobuf比较有用的地方之一,另外一个比较有用的地方是,它可以根据类型信息去create对象,比如 之前我定义的helloworld结构,假如我现在在做一个服务端的程序,我想实现这样的一个功能,在接收到helloworld消息的时候,我去执行相应的helloworld回调。当然对于helloworld的回调是在系统启动的时候加载的。这样有一个好处,实现整个系统的模块化,并且耦合度也很低。没有这个工具的话,我可能就需要在解析的时候特别的关注收到的消息类型字段,然后根据该字段去new一个相应的对象,进而调用相应的处理过程,整个过程都需要你去手写,有了protobuf,你完全不需要造*了。
那么怎么样根据消息的类型直接创建相应的对象呢?代码如下:
google::protobuf::Message* create_message(const std::string& type_name)
{
google::protobuf::Message* message = NULL;
const google::protobuf::Descriptor* descriptor = \
google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(type_name);
if(descriptor)
{
const google::protobuf::Message* prototype =
google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
if(prototype)
{
message = prototype->New();
}
}
return message;
}
有点设计模式里工厂模式的味道。在消息的种类不多的时候,你可以感觉不到它的好处,但在你的系统越来越庞大的时候,你自然能体会到这个小小函数的甜头。
在你知道消息类型,并根据这个工具生成消息对象的时候,接下来就该处理了,也就是dispatch。上面我有提到过,你可以注册每个消息类型的回调处理方法,通俗点讲就是你的系统里需要有一个map,这个map里存储了不同消息的处理方法,类似于:
std::map<std::string, message_callback_t> _message_callbacks; 这样的一个容器。具体的方法如下:
void deal_message(google::protobuf::Message* msg)
{
auto iter_map = _message_callbacks.find(msg->GetTypeName()); //得到消息的具体类型,protobuf提供的内置方法
if(iter_map != _message_callbacks.end())
{
iter_map->second(msg); //相应的回调处理
}
else
{
std::cout << "message dealer no found" << std::endl;
}
}
你看这样是不是简直帅呆了,你的系统如果用这样的方法来执行消息分发处理,整个系统将会变得很清晰,模块间耦合很低。
基于以上的思路,我简单实现了一个client和server,来示例本博文的思路,源代码地址:https://github.com/xiaopeifeng/CodeTricks/tree/master/protobuf
欢迎指正批评。