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

时间:2014-07-11 10:16:04

标签: c++ sockets boost tcp boost-asio

我目前正在使用Boost Asio实施网络协议。域类已经存在,我能够

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

网络数据包包含网络数据包标头。标头以数据包长度字段开头,字段大小为两个字节(std::uint16_t)。

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

  1. 读取数据包的长度以了解其总长度。这意味着从套接字中读取两个字节。
  2. 读取数据包的其余部分。这意味着从套接字中精确读取kActualPacketLength - sizeof(PacketLengthFieldType)个字节。
  3. Concat都读取二进制数据。
  4. 因此我需要至少两次调用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::read连续两次拨打boost::asio::streambuf,我无法让它工作。

    以下是代码:

    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个实例,我不知道如何“连接”/“追加”它们。
    3. 在一天结束时,我需要一个std::istream,其中包含从套接字中获取的正确数据。

      有人可以指导我进入正确的方向吗?我已经尝试过几个小时了...

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

      谢谢!

2 个答案:

答案 0 :(得分:3)

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

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

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

    现在您可以将b丢弃,因为它的待处理数据已附加到a

  3. 查看 Live on Coliru

答案 1 :(得分:0)

在我忘记之前,我想总结一下当前的解决方案,它没有使用boost::asio::streambuf,因为在没有修改它的情况下似乎无法从中读取它。相反,我使用std::vector<std::uint8_t>ByteVector)作为缓冲区的数据持有者。

以下源代码包含我当前的解决方案:

Packet const ReadPacketFromSocket() {
  ByteVector const kPacketLengthData{this->ReadPacketLengthFromSocket()};
  PacketHeader::PacketLengthType kPacketLength{
      static_cast<PacketHeader::PacketLengthType>(
          (kPacketLengthData[1] << 8) | kPacketLengthData[0])};

  ByteVector rest_packet_data(Packet::KRecommendedMaximumSize);
  boost::asio::read(
      this->socket_,
      boost::asio::buffer(rest_packet_data),
      boost::asio::transfer_exactly(
          kPacketLength - sizeof(PacketHeader::PacketLengthType)));

  ByteVector data{
      VectorUtils::GetInstance().Concatenate(
          kPacketLengthData,
          rest_packet_data)};

  // Create an input stream from the vector.
  std::stringstream input_stream;
  input_stream.rdbuf()->pubsetbuf(
      reinterpret_cast<char *>(&data[0]), data.size());

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

  return kPacketReader.Read(input_stream);
}

ByteVector ReadPacketLengthFromSocket() {
  ByteVector data_holder(sizeof(PacketHeader::PacketLengthType));

  boost::asio::read(
      this->socket_,
      boost::asio::buffer(data_holder),
      boost::asio::transfer_exactly(sizeof(PacketHeader::PacketLengthType)));

  return data_holder;
}

这就像魅力一样,我使用这种方法在两个进程之间成功地使用来自我的域模型的消息交换了数据包。

但是:此解决方案感觉错误,因为我必须进行大量转换。也许其他人可以为我提供更清洁的方法?您如何看待我的解决方案?