一. 消息识别

为了达到消息识别的目的,首先需要定义消息格式:

 Muduo + ProtoBuffer的使用_字节流

 len : 消息长度

 namelen : 消息类型名长度

 messageName : 消息类型名

 protoBufData : 消息体数据

 checkSum : 校验和 (这里使用 adler32 算法)

 注意:使用ProtoBuffer进行传输是不需要消息的版本号的,因为ProtoBuffer支持optional字段,可以通过增加或减少optional字段来实现不同版本的消息,而且不会引起解析错误。



二. 消息解析

当处理端收到消息后,应该怎样处理了,注意处理端收到的字节流很可能不能构成一个完整的消息,也可能构成多个消息

       对数据的解码在ProtobufCodec类中,onMessage函数就是处理原始字节流的,parse函数从原始字节流中提取出消息


  1. void ProtobufCodec::onMessage(const TcpConnectionPtr& conn,
  2. Buffer* buf,
  3. Timestamp receiveTime)
  4. {
  5. while (buf->readableBytes() >= kMinMessageLen + kHeaderLen)
  6. {
  7. // 1. 获取消息长度 (头四个字节)
  8. const int32_t len = buf->peekInt32();
  9. if (len > kMaxMessageLen || len < kMinMessageLen)
  10. {
  11. errorCallback_(conn, buf, receiveTime, kInvalidLength);
  12. break;
  13. }
  14. else if (buf->readableBytes() >= implicit_cast<size_t>(len + kHeaderLen))
  15. {
  16. ErrorCode errorCode = kNoError;
  17. // 2. parse生成具体消息
  18. MessagePtr message = parse(buf->peek()+kHeaderLen, len, &errorCode);
  19. if (errorCode == kNoError && message)
  20. {
  21. // 3. 调用消息处理函数
  22. messageCallback_(conn, message, receiveTime);
  23. // 4. 从缓冲区中删除已处理的消息字节数据
  24. buf->retrieve(kHeaderLen+len);
  25. }
  26. else
  27. {
  28. errorCallback_(conn, buf, receiveTime, errorCode);
  29. break;
  30. }
  31. }
  32. else
  33. {
  34. break;
  35. }
  36. }
  37. }


  38. MessagePtr ProtobufCodec::parse(const char* buf, int len, ErrorCode* error)
  39. {
  40. MessagePtr message;

  41. // check sum
  42. // 1. 检查校验和
  43. int32_t expectedCheckSum = asInt32(buf + len - kHeaderLen);
  44. int32_t checkSum = static_cast<int32_t>(
  45. ::adler32(1,
  46. reinterpret_cast<const Bytef*>(buf),
  47. static_cast<int>(len - kHeaderLen)));
  48. if (checkSum == expectedCheckSum)
  49. {
  50. // get message type name
  51. // 2. 获取消息类型名长度
  52. int32_t nameLen = asInt32(buf);
  53. if (nameLen >= 2 && nameLen <= len - 2*kHeaderLen)
  54. {
  55. // 3. 获取消息类型名
  56. std::string typeName(buf + kHeaderLen, buf + kHeaderLen + nameLen - 1);
  57. // create message object
  58. // 4. 根据消息类型名创建一个消息
  59. message.reset(createMessage(typeName));
  60. if (message)
  61. {
  62. // parse from buffer
  63. const char* data = buf + kHeaderLen + nameLen;
  64. int32_t dataLen = len - nameLen - 2*kHeaderLen;
  65. // 5. 从原始字节流中反序列化出消息数据
  66. if (message->ParseFromArray(data, dataLen))
  67. {
  68. *error = kNoError;
  69. }
  70. else
  71. {
  72. *error = kParseError;
  73. }
  74. }
  75. else
  76. {
  77. *error = kUnknownMessageType;
  78. }
  79. }
  80. else
  81. {
  82. *error = kInvalidNameLen;
  83. }
  84. }
  85. else
  86. {
  87. *error = kCheckSumError;
  88. }

  89. return message;
  90. }

这里很重要的一点就是如何根据消息类型名创建一个消息,createMessage完成这个功能


  1. google::protobuf::Message* ProtobufCodec::createMessage(const std::string& typeName)
  2. {
  3. google::protobuf::Message* message = NULL;
  4. const google::protobuf::Descriptor* descriptor =
  5. google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(typeName);
  6. if (descriptor)
  7. {
  8. const google::protobuf::Message* prototype =
  9. google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
  10. if (prototype)
  11. {
  12. message = prototype->New();
  13. }
  14. }
  15. return message;
  16. }


该函数用到的几个重要的步骤:


  1. 获取 MessageFactory 对象
    MessageFactory 类提供了一个 generated_factory() 的静态函数,此静态函数可以获取一个 MessageFactory 对象,此 MessageFactory 对象能够用来创建被编译入程序的所有的 message 对象。注意,此 Factory 是一个 Singleton,因此重复多次调用 generated_factory 函数不会创建多个 MessageFactory 对象,另外调用者也不能通过调用 delete 删除此对象。
  2. 获取 DescriptorPool 对象
    通过 DescriptorPool 类的 generated_pool() 静态函数能够获取 DescriptorPool 的指针。此 DescriptorPool 中包含了被编译入程序的 message 的 descriptor。generated_pool 类似于 generated_factory 函数,可以被重复调用多次而不会创建多个 DescriptorPool 对象。
  3. 获取 message descriptor
    有了 DescriptorPool 对象就可以获取到 message 的 descriptor 了。常见的一个函数是 const Descriptor * FindMessageTypeByName(const string & name) const,此函数可以通过 message 名字获取到顶层 message 的 descriptor。当然除此之外还有一些 API 可以用来获取 message discriptor,可以参考相关文档,这里就不一一详述了。
  4. 获取 message prototype 并构建 message 对象
    前面已经讲述了获取 MessageFactory 对象的方法,有了 MessageFactory 对象就可以通过函数 MessageFactory::GetPrototype(const Descriptor * type) 获取 message prototype(实质上就是一个 message 对象)。通过调用 message prototype 的 New 函数则可以构造此类型的 message。
    对同一个 Descriptor 多次调用 MessageFactory::GetPrototype 函数将返回同一个对象。通过调用 prototype 的 New 函数构造的 message 对象必须在 MessageFactory 销毁前销毁。

三. 消息处理

消息的处理是在函数messageCallBack_中进行,这个函数是我们在创建QueryServer时绑定的:


  1. QueryServer(EventLoop* loop,
  2. const InetAddress& listenAddr)
  3. : server_(loop, listenAddr, "QueryServer"),
  4. dispatcher_(boost::bind(&QueryServer::onUnknownMessage, this, _1, _2, _3)),

  5. // 将ProtobufDispatcher::onProtobufMessage 绑定到messageCallBack_上
  6. codec_(boost::bind(&ProtobufDispatcher::onProtobufMessage, &dispatcher_, _1, _2, _3))
  7. {
  8. // 注册的消息处理函数
  9. dispatcher_.registerMessageCallback<muduo::Query>(
  10. boost::bind(&QueryServer::onQuery, this, _1, _2, _3));
  11. dispatcher_.registerMessageCallback<muduo::Answer>(
  12. boost::bind(&QueryServer::onAnswer, this, _1, _2, _3));
  13. server_.setConnectionCallback(
  14. boost::bind(&QueryServer::onConnection, this, _1));
  15. server_.setMessageCallback(
  16. boost::bind(&ProtobufCodec::onMessage, &codec_, _1, _2, _3));
  17. }

onProtobufMessage根据每个类型的descriptor来从一张表中查找处理函数,因为每种类型都有一个全局的Descriptor对象,它的地址不变,因此使用它来查找注册的函数


  1. void onProtobufMessage(const muduo::net::TcpConnectionPtr& conn,
  2. const MessagePtr& message,
  3. muduo::Timestamp receiveTime) const
  4. {
  5. CallbackMap::const_iterator it = callbacks_.find(message->GetDescriptor());
  6. if (it != callbacks_.end())
  7. {
  8. it->second->onMessage(conn, message, receiveTime);
  9. }
  10. else
  11. {
  12. defaultCallback_(conn, message, receiveTime);
  13. }
  14. }

至此,我们就实现了消息的自解析和自处理。