随着通信技术和传感器技术的不断发展,数据采集与传输系统得到了越来越广泛的应用。而Google Protocol Buffers是Google公司开发是一款非常优秀的库,其定义了紧凑的、可扩展的二进制消息格式,特别适合用于数据传输。本文着重介绍了使用Protocol Buffers的对数据的封装和其反射机制来实现数据采集与传输系统的快速扩展采集数据类型。
1 Protocol Buffers概述
1.1 简介
Protocol Buffers(以下简称ProtoBuf)是由Google开发的一种数据描述语言。ProtoBuf定义了一种紧凑的可扩展二进制消息格式,能对结构化的数据进行灵活的、高效的、自动的机制来进行序列化。ProtoBuf可扩展方式的序列化结构数据被广泛应用在通信协议、数据存储等领域。
1.2 ProtoBuf的性能
一条消息数据,用ProtoBuf序列化后的大小是JSON的十分之一,是XML格式的二十分之一,是二进制序列化的十分之一。总体看来ProtoBuf的优势还是非常明显的。
2 应用在数据采集与传输系统中
这里所设计的数据采集与传输系统采用Slave-Master结构。其中Slave负责采集数据并将数据发送给Master;Master接收所采集的数据并做进一步处理。Slave可以支持多种数据类型(如GPS、图像等)的采集。
2.1 根据不同的采集数据类型,编写proto文件
在ProtoBuf中,所有的对象都被视为消息。消息的每个属性描述都可以使用required、optional、repeated来进行描述。ProtoBuf数据描述语言中也支持一些基本的数据类型如string、int32、double等等。
设Slave的采集数据类型有Type1、Type2。这两种类型的Proto描述命名为MsgType1和MsgType2(图1所示)。
经proto编译后,生成的消息类为MsgType1和MsgType2,它们均继承自google::protobuf::Message类。
2.2 设计支持不同采集数据类型的数据传输格式
在数据传输中使用ProtoBuf需要解决两个问题,一是数据的长度:ProtoBuf打包的数据没有自带长度信息或终结符,这就需要由应用程序自己在发生和接收的时候做正确的分割;二是消息类型:ProtoBuf打包的数据没有自带的类型信息,在消息传输过程中,发送方需要将消息类型告诉接收方,接收方根据消息类型再做反序列化。对于长度问题,可以将长度信息作为消息的一个段来解决。而对于消息类型问题,可以使用ProtoBuf根据消息的类型名反射自动创建对应的消息对象的机制来解决。因此,可以设计基本传输格式的格式如图2所示:
ProtoBuf Message的序列化数据封装在message_data中,且称这种数据格式为Message Package(消息包)。
2.3 消息打包器的设计
消息包格式设计完后,首先要对不同的采集数据类型编写封装函数,以便将相应类型的数据封装到对应的ProtoBuf Message中。然后使用消息打包器将Slave所采集的某种类型的数据信息打包成上图的消息包。消息打包器先通过ProtoBuf将特定类型的采集数据进行序列化,并生填充Message Data。最后再填充Message Package中的Length 、Message Name等字段,完成消息的打包操作。消息打包器代码如下:
std::string CreateMsgPackage( const google::protobuf::Message& msg )
{
std::string msg_pack;
msg_pack.resize( sizeof( int32_t ) );
string& msg_name = msg.GetTypeName();
int32_t name_len = msg_name.size()+1;
msg_pack.append((char*)&name_len,sizeof(name_len));
msg_pack.append(msg_name.c_str(),name_len);
Msg.AppendToString(&msg_pack);
char* begin = msg_pack.c_str()+sizeof( int32_t );
int32_t length = msg_pack.size()-sizeof(int32_t);
std::copy( (char*)( &length ), (char*)( Length ) +
sizeof( Length ), msg_pack.begin );
return msg_pack;
}
2.4 消息解包器的设计
接收到消息包之后要进行解封装,分解出消息包中的各个字段,这里不再详述。ProtoBuf本身具有很强的反射机制,ProtoBuf可以能根据Message Name创建一个该类型的消息,然后使用Message Data来反序列化该消息,从而在Message Package中恢复出相应类型的Message,由此完成对消息的识别。由消息包来还原相应的消息的代码如下:
Message* CreateMsg( std::string& msg_pack )
{
// 从msg_pack中分离msg_name、msg_data等的代码从略
Message* msg = NULL;
Descriptor* desc = DescriptorPool::generated_pool()->FindMessageTypeByName(msg_name);
Message* prototype = MessageFactory::generated_factory()->GetPrototype(desc);
msg = prototype->New();
msg->ParseFromArray(msg_data, msg_data_len);
return msg;
}
2.5 消息分发器的设计
Master在得到相应类型的采集数据消息后,需要传递给相应的消息处理方法,这就涉及到消息的分发。消息分发器可以使用map来实现,由于每个具体消息类型都有一个全局的Descriptor对象,其地址是唯一的,可作为key;value为针对特定采集数据类型消息的处理函数,即std::map,其中MessageCallBack为 boost::function。由于消息分发器传给处理函数的参数是Message*类型,处理函数需要对其进行向下转型后才能使用。消息分发器在接收到某一消息后,在map中查找对应的处理函数,并执行该函数。
2.6 整体结构
在Slave端,用户需要使用proto数据描述语言描述该类型的数据,并产生相应的Message类型,此外用户还要编写相应数据类型消息封装方法。在Master端,由于与Slave使用相同的proto文件,消息解包器可以分辨出相应类型的Message。用户在Master端需要编写针对某具体类型采集数据的处理方法,并向消息分发器注册。消息分发器将消息解包器解出的消息作为参数调用对应的处理方法。
3 结束语
在Slave-Master结构的系统中通过编写proto文件来描述各种类型的采集数据;在Slav e端进行采集数据的序列化和封装;在Master端编写对应的采集数据处理方法,并将该方法注册到Master的消息分发器中,完成对采集数据类型的快速扩展。
Protocol Buffers .https://developers.google.com/protocol-buffers/docs/overview.
陈硕.Linux多线程服务端编程——使用muduo C++网络库.北京:电子工业出版社.2013:220-236.
李纪欣,王康,周立法,章军.Google Protobuf在Linux Socket通讯中的应用.电脑开发与应用.2013,26(4).
田源,潘晨光,丁杰.Protocol Buffers在即时通讯系统中的应用研究 .现代电子技术.2013,37(5)