【问题标题】:Sending Protobuf Messages with boost::asio使用 boost::asio 发送 Protobuf 消息
【发布时间】:2011-01-26 21:06:06
【问题描述】:

我正在尝试使用 Google 的协议缓冲区和 boost::asio 在 C++ 中一起破解客户端。

我的问题是我不知道如何将 protobuf 消息提供给 asio。 我有的是这样的:

// set up *sock - works
PlayerInfo info;
info.set_name(name);
// other stuff

现在我知道以下是错误的,但我还是会发布它:

size_t request_length = info.ByteSize();
boost::asio::write(*sock, boost::asio::buffer(info, request_length));

据我所知,我必须以不同的方式将消息打包到缓冲区中 - 但是如何?

一般来说,我很难弄清楚 boost::asio 的工作原理。有一些教程,但它们通常只涵盖发送标准数据格式,例如 int,它可以开箱即用。我认为我的问题是 序列化,但另一方面,我了解到 protobuf 应该为我做这件事......现在我很困惑;)

感谢您的帮助!

--> Daniel Gehriger 提供了解决方案,非常感谢!

【问题讨论】:

    标签: c++ networking boost boost-asio protocol-buffers


    【解决方案1】:

    我不太了解 Google 的协议缓冲区,但请尝试以下操作:

    PlayerInfo info;
    info.set_name(name);
    // ...
    
    boost::asio::streambuf b;
    std::ostream os(&b);
    info.SerializeToOstream(&os);
    
    boost::asio::write(*sock, b);
    

    【讨论】:

    • 您需要向流中添加更多信息,以便可靠地读取另一端的 PlayerInfo 对象。 Protobuf 不直接在流中嵌入对象类型。我认为它也不包含长度,但我必须检查文档。顺便说一句,ASIO 不知道对象;它只处理字节。阅读示例和文档时请记住这一点。
    • Dan:是的,我想我必须事先添加大小,以便服务器能够读取它...谢谢!
    • @adi64:太好了!也许你想在你的问题中提到这一点,以防它对其他人有用。
    • @adi64 - 感谢您的感谢!但是我想说你提到一个人也必须事先发送尺寸。这部分代码可能很有趣。
    • 大小前缀数据包是每个人在网络通信中首先尝试的,它在世界干净的情况下工作。但请注意,如果您需要重新同步流(即,您不确定自己在哪里),它不会让您摆脱麻烦。这就是为什么健壮的协议经常依赖于分隔符,尽管有转义的麻烦。
    【解决方案2】:

    我刚刚开始使用Google Protocol Buffers (protobuf),并且在通过计算机网络发送(和接收)消息时遇到了问题。

    与 Java API 相比,C++ API 没有 writeDelimitedTo 方法来发送带有分隔符的 protobuf 消息。 如果没有分隔符,我们还必须发送消息的大小,以便能够在接收端点对其进行反序列化。

    C++ API 提供类::google::protobuf::io::CodedOutputStream,在头文件google/protobuf/io/coded_stream.h 中定义。

    以下源代码演示了如何通过网络通过Boost.Asio 发送分隔的protobuf 消息。该示例使用 UDP。由于我还没有在 WWW 上找到一个可行的例子,所以我在这里分享它。

    #include "boost/asio.hpp"
    #include "google/protobuf/io/coded_stream.h"
    #include "google/protobuf/io/zero_copy_stream_impl.h"
    
    using ::boost::asio::ip::udp;
    
    int main() {
      PlayerInfo message;
      message.set_name("Player 1");
      // ...
    
      const boost::asio::ip::address_v4 kIpAddress = boost::asio::ip::address_v4::loopback();
      const unsigned short kPortNumber = 65535;
    
      try {
        boost::asio::io_service io_service;
        udp::socket socket(io_service, boost::asio::ip::udp::v4());
    
        udp::endpoint endpoint(kIpAddress, kPortNumber);
        boost::system::error_code error;
    
        boost::asio::streambuf stream_buffer;
        std::ostream output_stream(&stream_buffer);
    
        {
          ::google::protobuf::io::OstreamOutputStream raw_output_stream(&output_stream);
          ::google::protobuf::io::CodedOutputStream coded_output_stream(&raw_output_stream);
          coded_output_stream.WriteVarint32(message.ByteSize());
    
          message.SerializeToCodedStream(&coded_output_stream);
          // IMPORTANT: In order to flush a CodedOutputStream it has to be deleted,
          // otherwise a 0 bytes package is send over the wire.
        }
      }
    
      size_t len = socket.send_to(stream_buffer.data(), endpoint, 0, error);
    
      if (error && error != boost::asio::error::message_size) {
        throw boost::system::system_error(error);
      }
    
      std::cout << "Sent " << len << " bytes data to " << kIpAddress.to_string() << "." << std::endl;
    } catch (const std::exception& ex) {
      std::cerr << ex.what() << std::endl;
    }
    

    在写这篇文章的过程中,我还发现了以下两个问题:

    两者都与此问题相关,并且还包含(部分)答案。无论如何,我希望我的回答可能有用。

    【讨论】:

      【解决方案3】:
      • 使用asio::streambuf存储数据(也可以包含自定义header)
      • 然后使用boost::smart_ptr::local_shared_ptr() 单线程或std::shared_ptr 委托数据所有者。

      然后随时发送,例如:

      boost::local_shared_ptr<asio::streambuf> wbuf;
      asio::async_write(sock, *wbuf, [&, wbuf](const asio::error_code &ec, std::size_t len){});
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-13
        • 1970-01-01
        相关资源
        最近更新 更多