【问题标题】:C++ Linux Google Protobuf + boost::asio Cannot ParseC++ Linux Google Protobuf + boost::asio 无法解析
【发布时间】:2015-10-14 09:04:59
【问题描述】:

我正在尝试通过 TCP 通过 boost::asio 套接字发送 Google Protobuf 消息。我认识到 TCP 是一种流协议,因此我在消息通过套接字之前对其执行长度前缀。我有代码工作,但它似乎只在某些时候工作,即使我重复相同的调用而不改变环境。有时我会收到以下错误:

[libprotobuf ERROR google/protobuf/message_lite.cc:123] 无法解析“xxx”类型的消息,因为它缺少必填字段:名称、应用程序类型、消息类型

原因很容易理解,但我不能单独指出为什么这种情况有时会发生并且在大多数情况下解析得很好。只需让一个客户端与服务器通信并简单地重新启动进程,就很容易复制错误。

下面是socket代码sn-ps。

const int TCP_HEADER_SIZE = 8;

发件人:

bool Write(const google::protobuf::MessageLite& proto) {
    char header[TCP_HEADER_SIZE];
    int size = proto.ByteSize();
    char data[TCP_HEADER_SIZE + size];
    sprintf(data, "%i", size);
    proto.SerializeToArray(data+TCP_HEADER_SIZE, size);
    boost::asio::async_write(Socket, 
                             boost::asio::buffer(data, TCP_HEADER_SIZE + size),
                             boost::bind(&TCPSender::WriteHandler, 
                                         this, _1, _2));
}

接收者:

std::array<char, TCP_HEADER_SIZE> Header;
std::array<char, 8192> Bytes;

void ReadHandler(const boost::system::error_code &ec, 
                 std::size_t bytes_transferred) {
    if(!ec) {
        int msgsize = atoi(Header.data());
        if(msgsize > 0) {
            boost::asio::read(Socket, boost::asio::buffer(Bytes,static_cast<std::size_t>(msgsize)));
            ReadFunc(Bytes.data(), msgsize);
        }
        boost::asio::async_read(Socket, boost::asio::buffer(Header, TCP_HEADER_SIZE),
                                boost::bind(&TCPReceiver::ReadHandler, this, _1, _2));
    }
    else {
        std::cerr << "Server::ReadHandler::" << ec.message() << '\n';
    }
}

ReadFunc:

void HandleIncomingData(const char *data, const std::size_t size) {
    xxx::messaging::CMSMessage proto;
    proto.ParseFromArray(data, static_cast<int>(size));
}

我应该提一下,我需要它尽可能快,所以任何优化都将非常感谢。

【问题讨论】:

  • 略读,我没有看到明显的问题,但关于优化,请查看MessageLite::SerializeWithCachedSizesToArray()。您当前的代码实际上计算了两次ByteSize()。如果您还没有,也可以尝试在调试模式下编译(没有-DNDEBUG);然后将在序列化时(而不仅仅是解析时)检查必填字段,这样您就可以排除这方面的错误。
  • 谢谢你,我没有意识到protobuf已经内置了这样的优化。我将不得不考虑更多地使用 CodedOutputStream,但我认为这可能是正确的轨道。
  • 如果您查看 message_lite.cc 中的定义,您可以了解各种序列化/解析方法在底层做了什么,而无需太多工作。通常,如果您了解包装器方法的作用,则不需要直接使用“编码流”。

标签: c++ linux tcp boost-asio protocol-buffers


【解决方案1】:

程序调用未定义的行为,因为它无法满足boost::asio::async_write()buffers 参数的生命周期要求:

[...] 底层内存块的所有权由调用者保留,它必须保证它们在调用处理程序之前保持有效。

Write() 函数中,boost::asio::async_write() 将立即返回,并可能导致data 在异步写入操作完成之前超出范围。要解决此问题,请考虑延长底层缓冲区的寿命,例如通过将缓冲区与操作相关联并在处理程序中执行清理,或使缓冲区成为 TCPSender 上的数据成员。

【讨论】:

  • 就是这样,我延长了缓冲区变量数据的生命周期,它已经解决了这个问题。它让我忘记了 async_write 在离开函数之前可能无法完成(尽管这就是重点)。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-22
  • 2020-09-11
  • 1970-01-01
  • 2012-06-12
  • 1970-01-01
相关资源
最近更新 更多