当前位置:   article > 正文

muduo中实现Protobuf编解码器codec与消息分发器dispatcher_muduo protobuf

muduo protobuf

前言

Google Protocol Buffers(简称Protobuf)是一款非常优秀的库,它定义了一种紧凑(相对XML和JSON而言)的可扩展二进制消息格式,特别适合网络数据传输。

网络编程中使用Protobuf需要解决以下两个问题:

  • 长度。Protobuf打包的数据没有自带长度信息或终结符,需要由应用程序自已在发生和接收的时候做正确的切分。
  • 类型。Protobuf打包的数据没有自带类型信息,需要由发送方把类型信息传给接收方,接收方创建具体的Protobuf Message对象,再做反序列化。

这里的第一个问题很好解决,通常的做法是在每个消息前面加个固定长度的length header。第二个问题也很好解决,因为Protobuf本身具有很强的反射功能,可以根据type name创建具体类型的Message对象。其具体做法是:

  • 用DescriptorPool::generated_pool()找到一个DescriptorPool对象,它包含了程序编译时所链接的全部Protobuf Message types。
  • 根据type name用DescriptorPool::FindMessageTypeByName()查找Descriptor。
  • 再用MessageFactory::generated_factory()找到MessageFactory对象,它能创建程序编译时所链接的全部Protobuf Message types。
  • 然后用MessageFactory::GetPrototype()找到具体Message type的default instance。
  • 最后用prototype->New()创建对象。

相应的代码为:

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

以上关于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
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

编解码器ProtobufCodec

编解码器(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。

实现ProtobufCodec

根据上文定义的消息格式,我们的编码算法很直截了当,其代码为:

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/634120
推荐阅读
相关标签
  

闽ICP备14008679号