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

Retrieve correct data with two consecutive calls to boost::asio::read

本文关键字:read asio 检索 数据 boost 连续 调用 两次      更新时间:2023-10-16

我目前正在与Boost Asio实现网络协议。域类已经存在,我可以

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

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

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

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

因此,我需要至少两次调用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

在我忘记之前,我想总结一下我目前的解决方案,它不使用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;
}

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

但是:这个解决方案感觉不对,因为我必须做很多转换。也许其他人能给我提供一个更简洁的方法?你觉得我的解决方案怎么样?