2
votes

My code is as follows:

boost::asio::streambuf b1;

boost::asio::async_read_until(upstream_socket_, b1, '@',
           boost::bind(&bridge::handle_upstream_read, shared_from_this(),
           boost::asio::placeholders::error,
           boost::asio::placeholders::bytes_transferred));


void handle_upstream1_read(const boost::system::error_code& error,
                            const size_t& bytes_transferred)
  {
     if (!error)
     {

       async_write(downstream_socket_,
          b2,
          boost::bind(&bridge::handle_downstream_write,
          shared_from_this(),
          boost::asio::placeholders::error));  
     }
     else
        close();
  }

According to the documentation of async_read_until, http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/reference/async_read_until/overload1.html, After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter. An application will typically leave that data in the streambuf for a subsequent async_read_until operation to examine.

I know that the streambuf may contain additional data beyond the delimiter, but, in my case, will it write those additional data (the data beyond the char'@') to the downstream_socket_ inside the async_write operation? Or will async_write function be smart enough not to write those additional data until the next time the handle_upstream1_read function is being called?

According to the approaches in the documentation, the data in streambuf are stored in the istream first ( std::istream response_stream(&streambuf); ) and then put it into a string by using std::getline() funciton.

Do I really need to store the streambuf in istream first and then convert it into a string and then convert it back to char arrary (so that I can send the char array to the downstream_socket_ ) instead of just using the async_write to write the data( up to but not including the delimter, '@' ) to the downstream_socket_ ?

I prefer the second approach so that I don't need to make several conversion on the data. However, it seems that something is wrong when I tried the second approach.

My ideal case is that:

  1. upstream_socket_ received xxxx@yyyy by using async_read_until
  2. xxxx@ is written to the downstream_socket_
  3. upstream_socket_ received zzzz@kkkk by using async_read_until
  4. yyyyzzzz@ is written to the downstream_socket_

It seems that async_write operation still writes the data beyond the delimiter to the downstream_socket_. (but I am not 100% sure about this)

I appreciate if anyone can provide a little help !

1

1 Answers

3
votes

The async_write() overload being used is considered complete when all of the streambuf's data, its input sequence, has been written to the WriteStream (socket). It is equivalent to calling:

boost::asio::async_write(stream, streambuf,
    boost::asio::transfer_all(), handler);

One can limit the amount of bytes written and consumed from the streambuf object by calling this async_write() overload with the boost::asio::transfer_exactly completion condition:

boost::asio::async_write(stream, streambuf, 
    boost::asio::transfer_exactly(n), handler);

Alternatively, one can write directly from the streambuf's input sequence. However, one will need to explicitly consume from the streambuf.

boost::asio::async_write(stream,
    boost::asio::buffer(streambuf.data(), n), handler);
// Within the completion handler...
streambuf.consume(n);

Note that when the async_read_until() operation completes, the completion handler's bytes_transferred argument contains the number of bytes in the streambuf's input sequence up to and including the delimiter, or 0 if an error occurred.


Here is a complete example demonstrating using both approaches. The example is written using synchronous operations in an attempt to simplify the flow:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

// This example is not interested in the handlers, so provide a noop function
// that will be passed to bind to meet the handler concept requirements.
void noop() {}

/// @brief Helper function that extracts a string from a streambuf.
std::string make_string(
  boost::asio::streambuf& streambuf,
  std::size_t n)
{
  return std::string(
      boost::asio::buffers_begin(streambuf.data()),
      boost::asio::buffers_begin(streambuf.data()) + n);
}

int main()
{
  using boost::asio::ip::tcp;
  boost::asio::io_service io_service;

  // Create all I/O objects.
  tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
  tcp::socket server_socket(io_service);
  tcp::socket client_socket(io_service);

  // Connect client and server sockets.
  acceptor.async_accept(server_socket, boost::bind(&noop));
  client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop));
  io_service.run();

  // Mockup write_buffer as if it read "xxxx@yyyy" with read_until()
  // using '@' as a delimiter.
  boost::asio::streambuf write_buffer;
  std::ostream output(&write_buffer);
  output << "xxxx@yyyy";
  assert(write_buffer.size() == 9);
  auto bytes_transferred = 5;

  // Write to server.
  boost::asio::write(server_socket, write_buffer,
      boost::asio::transfer_exactly(bytes_transferred));
   // Verify write operation consumed part of the input sequence.
  assert(write_buffer.size() == 4);

  // Read from client.
  boost::asio::streambuf read_buffer;
  bytes_transferred = boost::asio::read(
      client_socket, read_buffer.prepare(bytes_transferred));
  read_buffer.commit(bytes_transferred);

  // Copy from the read buffers input sequence.
  std::cout << "Read: " << 
               make_string(read_buffer, bytes_transferred) << std::endl;
  read_buffer.consume(bytes_transferred);

  // Mockup write_buffer as if it read "zzzz@kkkk" with read_until()
  // using '@' as a delimiter.
  output << "zzzz@kkkk";
  assert(write_buffer.size() == 13); 
  bytes_transferred = 9; // yyyyzzzz@

  // Write to server.
  boost::asio::write(server_socket, buffer(write_buffer.data(),
      bytes_transferred));
  // Verify write operation did not consume the input sequence.
  assert(write_buffer.size() == 13);
  write_buffer.consume(bytes_transferred);

  // Read from client.
  bytes_transferred = boost::asio::read(
      client_socket, read_buffer.prepare(bytes_transferred));
  read_buffer.commit(bytes_transferred);

  // Copy from the read buffers input sequence.
  std::cout << "Read: " << 
               make_string(read_buffer, bytes_transferred) << std::endl;
  read_buffer.consume(bytes_transferred);
}

Output:

Read: xxxx@
Read: yyyyzzzz@

A few other notes:

  • The streambuf owns the memory, and std::istream and std::ostream use the memory. Using streams may be a good idea when one needs to extract formatted input or insert formatted output. For instance, when one wishes to read the string "123" as an integer 123.
  • One can directly access the streambuf's input sequence and iterate over it. In the example above, I use boost::asio::buffers_begin() to help construct a std::string by iterating over a streambuf's input sequence.

    std::string(
        boost::asio::buffers_begin(streambuf.data()),
        boost::asio::buffers_begin(streambuf.data()) + n);
    
  • A stream-based transport protocol is being used, so handle incoming data as a stream. Be aware that even if the intermediary server reframes messages and sends "xxxx@" in one write operation and "yyyyzzzz@" in a subsequent write operation, the downstream may read "xxxx@yyyy" in a single read operation.