c++ - 通过连续两次调用 boost::asio::read 检索正确的数据

标签 c++ sockets boost tcp boost-asio

我目前正在使用 Boost Asio 实现网络协议(protocol).域类已经存在,我能够

  • 将数据包写入std::istream
  • 并从 std::ostream 中读取数据包。

一个网络包包含一个网络包头。 header 以 Packet Length 字段开始,其大小为两个字节 (std::uint16_t)。

我使用 TCP/IPv4 作为传输层,因此我尝试实现以下内容:

  1. 读取数据包的长度以了解其总长度。这意味着从套接字中读取两个字节。
  2. 阅读数据包的其余部分。这意味着从套接字中准确读取 kActualPacketLength - sizeof(PacketLengthFieldType) 个字节。
  3. Concat 读取二进制数据。

因此我至少需要调用两次 boost::asio::read(我是同步开始的!)。

如果我对预期长度进行硬编码,我可以通过调用 boost::asio::read 来读取数据包:

Packet const ReadPacketFromSocket() {
    boost::asio::streambuf stream_buffer;    
    boost::asio::streambuf::mutable_buffers_type buffer{
        stream_buffer.prepare(Packet::KRecommendedMaximumSize)};
    std::size_t const kBytesTransferred{boost::asio::read(
        this->socket_,
        buffer,
        // TODO: Remove hard-coded value.
        boost::asio::transfer_exactly(21))};
    stream_buffer.commit(kBytesTransferred);
    std::istream input_stream(&stream_buffer);
    PacketReader const kPacketReader{MessageReader::GetInstance()};

    return kPacketReader.Read(input_stream);
  }

这会立即读取完整的数据包数据并返回一个 Packet 实例。这行得通,所以这个概念行得通。

到目前为止一切顺利。现在我的问题:

如果我使用相同的 boost::asio::streambuf 连续两次调用 boost::asio::read,我将无法正常工作。

代码如下:

Packet const ReadPacketFromSocket() {
  std::uint16_t constexpr kPacketLengthFieldSize{2};

  boost::asio::streambuf stream_buffer;    
  boost::asio::streambuf::mutable_buffers_type buffer{
      stream_buffer.prepare(Packet::KRecommendedMaximumSize)};

  std::size_t const kBytesTransferred{boost::asio::read(
      // The stream from which the data is to be read.
      this->socket_,
      // One or more buffers into which the data will be read.
      buffer,
      // The function object to be called to determine whether the read
      // operation is complete.
      boost::asio::transfer_exactly(kPacketLengthFieldSize))};

  // The received data is "committed" (moved) from the output sequence to the
  // input sequence.
  stream_buffer.commit(kBytesTransferred);
  BOOST_LOG_TRIVIAL(debug) << "bytes transferred: " << kBytesTransferred;
  BOOST_LOG_TRIVIAL(debug) << "size of stream_buffer: " << stream_buffer.size();

  std::uint16_t packet_size;
  // This does seem to modify the streambuf!
  std::istream istream(&stream_buffer);
  istream.read(reinterpret_cast<char *>(&packet_size), sizeof(packet_size));
  BOOST_LOG_TRIVIAL(debug) << "size of stream_buffer: " << stream_buffer.size();
  BOOST_LOG_TRIVIAL(debug) << "data of stream_buffer: " << std::to_string(packet_size);

  std::size_t const kBytesTransferred2{
      boost::asio::read(
          this->socket_,
          buffer,
          boost::asio::transfer_exactly(packet_size - kPacketLengthFieldSize))};
  stream_buffer.commit(kBytesTransferred2);

  BOOST_LOG_TRIVIAL(debug) << "bytes transferred: " << kBytesTransferred2;
  BOOST_LOG_TRIVIAL(debug) << "size of stream_buffer: " << stream_buffer.size();

  // Create an input stream with the data from the stream buffer.
  std::istream input_stream(&stream_buffer);

  PacketReader const kPacketReader{MessageReader::GetInstance()};

  return kPacketReader.Read(input_stream);
}

我有以下问题:

  1. 在第一次套接字读取后从 boost::asio::streambuf 读取数据包长度似乎从 boost::asio::streambuf 中删除了数据。
  2. 如果我使用两个不同的 boost::asio::streambuf 实例,我不知道如何“连接”/“追加”它们。

归根结底,我需要一个包含从套接字获取的正确数据的 std::istream

有人可以指导我正确的方向吗?我已经尝试让这项工作几个小时了......

也许这种方法不是最好的,所以我愿意接受改进我的设计的建议。

谢谢!

最佳答案

  1. 我相信这种行为是设计使然的。

  2. 要连接缓冲区,您可以使用 BUfferSequences(使用 make_buffers)并使用缓冲区迭代器,或者您可以将第二个流式传输到第一个:

    boost::asio::streambuf a, b;
    std::ostream as(&a);
    
    as << &b;
    

    现在您可以丢弃 b,因为它的未决数据已附加到 a

查看 Live on Coliru

关于c++ - 通过连续两次调用 boost::asio::read 检索正确的数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24695395/

相关文章:

C++ cpp-net lib 未找到

c++ - Boost DFS如何保存访问过的顶点?

iphone - iPhone OS 4是否支持Web套接字?

sockets - 如何使用 UDP 找到正确的发送速度?

c++ - 如何从 boost int256_t 中获取以 2 为底的对数?

c++ - 使用 glm 从旋转矩阵获取角度?

c++ - std::launch::async 像同步进程一样阻塞

c++ - 多重定义,这里已经定义

c++ - make_shared、make_pair 等叫什么?

c - 写入h_addr_list