Question

Is there a way to read from a streambuf without removing the bytes?

I'm reading a 'message size' field from the buffer to check if the whole message was received.

If not, I'm posting another async read to get it, but the handler then has no way to know how long the message was supposed to be - because the size field was removed.

Any help appreciated!

E.g.

boost::asio::streambuf _buffer;

void onReceive(const boost::system::error_code& e, std::size_t bytesTransferred)
{
  if(e) return;

  if(_buffer.size() > 0)
  {
    // Partial message was previously received, but I don't know how long.
  }
  else
  {
    _buffer.commit(bytesTransferred);

    /* Read the size (and remove it from the stream) */
    unsigned short size = 0;
    std::istream in(&_buffer);
    in.read((char*)&size, sizeof(unsigned short);

    /* Got the whole message? */
    if(_buffer.size() > size)
    {
      /* Yes. */
    }
    else
    {
      /* No - read the rest. */
      boost::asio::async_read(/*...*/);
    }
  }
}
Was it helpful?

Solution

You can use read_async to initiate a read using the size of the message header and then adjust it in a 'completion condition' callback, like so:

typedef boost::system::error_code error_code;

template <typename Stream, typename Message>
void MessageReader<Stream, Message>::startRead()
{
  readBuffer = allocateMsg();
  async_read(stream, 
             boost::asio::buffer(readBuffer.get(), sizeof(*readBuffer)),
             boost::bind(&MessageReader<Stream, Message>::bytesToRead, this,
                         boost::asio::placeholders::error, 
                         boost::asio::placeholders::bytes_transferred),
             boost::bind(&MessageReader<Stream, Message>::readDone, this, 
                         boost::asio::placeholders::error, 
                         boost::asio::placeholders::bytes_transferred));
}

template <typename Stream, typename Message>
size_t MessageReader<Stream, Message>::bytesToRead(const error_code& error, 
                                                   size_t bytes_read)
{
  size_t result;

  if (error)
    result = 0;                                         // error - stop reading

  else if (bytes_read < sizeof(CmnMessageHeader))
    result = sizeof(CmnMessageHeader) - bytes_read;     // read rest of header

  else if (readBuffer->header.byteCount > sizeof(*readBuffer))
    result = 0;                                         // bad byte count

  else
    result = readBuffer->header.byteCount - bytes_read; // read message body

  return result;
}

template <typename Stream, typename Message>
void MessageReader<Stream, Message>::readDone(const error_code& error, 
                                              size_t bytes_read)
{
  if (error)
  {
    if (error.value() == boost::system::errc::no_such_file_or_directory)
    {
      notifyStop();
    }

    else if (error.value() != boost::system::errc::operation_canceled)
    {
      notifyStop();
    }

    // else the operation was cancelled, thus no stop notification is needed and
    // we can merely return
  }

  else if (bytes_read != readBuffer->header.byteCount)
  {
    LOG4CXX_ERROR(logger, "Message byte count mismatch");
    notifyStop();
  }

  else
  {
    handleMsg(readBuffer);
    startRead();
  }
}

EDIT: Added typedef for error_code.

OTHER TIPS

I did this yesterday. So i thought i'd offer my solution...

#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>

#include <boost/asio.hpp>
#include <boost/asio/streambuf.hpp>

void ReadFromStreambuf()
{
    boost::asio::streambuf mybuffer;

    // write some data to the buffer
    std::ostream o2buffer (&mybuffer);
    o2buffer << "hello stackoverflow";

    // get buffer size
    size_t nBufferSize = boost::asio::buffer_size(mybuffer.data());

    // get const buffer
    std::stringstream ssOut;
    boost::asio::streambuf::const_buffers_type constBuffer = mybuffer.data();

    // copy const buffer to stringstream, then output
    std::copy(
        boost::asio::buffers_begin(constBuffer),
        boost::asio::buffers_begin(constBuffer) + nBufferSize,
        std::ostream_iterator<char>(ssOut)
    );

    std::cout << ssOut.str() << "\n";
}


int main(int argc, char const *argv[])
{
    ReadFromStreambuf();
    return 0;
}

There are two approaches you can adopt:

  1. Issue a single read to read the number of bytes for the size (say 4), the issue a read for the required size.

  2. Use the read some call, and buffer the bytes in your code, say in a vector and analyse that way.

I would go for option 2, it does mean copying the buffer, however I would hazard that it is cheaper than multiple read some calls.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top