赞
踩
Google Protocol Buffers(简称Protobuf)是一款非常优秀的库,它定义了一种紧凑(相对XML和JSON而言)的可扩展二进制消息格式,特别适合网络数据传输。
在网络编程中使用Protobuf需要解决以下两个问题:
这里的第一个问题很好解决,通常的做法是在每个消息前面加个固定长度的length header。第二个问题也很好解决,因为Protobuf本身具有很强的反射功能,可以根据type name创建具体类型的Message对象。其具体做法是:
相应的代码为:
google::protobuf::Message* ProtobufCodec::createMessage(const std::string& typeName) { google::protobuf::Message* message = NULL; const google::protobuf::Descriptor* descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(typeName); if (descriptor) { const google::protobuf::Message* prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor); if (prototype) { message = prototype->New(); } } return message; }
以上关于Protobuf的C++ API的详解可以参考我的一篇博文Protobuf C++ API 简介
那么为什么Protobuf的默认序列化格式没有包含消息的长度和类型呢?这是为了节省网络带宽和存储,因为绝大多数情况下,我们可以根据其它的信息推断出消息的长度和类型。只有在使用TCP连接,且在一个连接上传递不止一种消息的情况下,才需要在消息中添加长度和类型信息。这时我们需要一个分发器dispatcher,把不同类型的消息分给各个消息处理函数。
陈硕先生在muduo库中设计了一个简单的打包格式,包含Protobuf data和其对应的长度和类型信息,消息的末尾还有一个check sum。格式如下图所示,图中方块的宽度是32-bit。
将该格式用C代码描述:
struct ProtobufTransportFormat __attribute__ ((__packed__))
{
int32_t len;
int32_t nameLen;
char typeName[nameLen];
char protobufData[len-nameLen-8];
int32_t checkSum; // adler32 of nameLen, typeName and protobufData
}
编解码器(codec)是encoder和decoder的缩写,这里借指“网络数据和业务消息之间互相转换”的代码。
codec的基本功能之一是做TCP分包:确定每条消息的长度,为消息划分界限。在非阻塞网络编程中,codec几乎是必不可少的。如果只收到了半条消息,那么不会触发消息事件回调,数据会停留在Buffer里,等待收到一个完整的消息再通知处理函数。
codec提供了一层间接性,它位于TcpConnection和TcpSever之间,拦截收到的数据(Buffer),在收到完整的消息之后,解出消息对象(std::string),再调用TcpServer对应的处理函数。另外在发送消息的时候,TcpServer通过send()来发送string,codec会负责将它编码成Buffer。
Protobuf codec与此非常类似,只不过消息类型从std::string变成了protobuf::Message。
根据上文定义的消息格式,我们的编码算法很直截了当,其代码为:
void ProtobufCodec::fillEmptyBuffer(Buffer* buf, const google::protobuf::Message& message)
{
// buf->retrieveAll();
assert(buf->readableBytes() == 0);
const std::string& typeName = message.GetTypeName();
int32_t nameLen = static_cast<int32_t>(typeName.size()+1)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。