Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::asio::async_read_until reads all data instead of just some

I'm modify the Boost Asio echo example to use async_read_until to read the input word by word. Even though I am using async_read_until, all the data sent seems to be read from the socket. Could someone please advise:

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

using boost::asio::ip::tcp;

class session
{
public:
  session(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
  }

  tcp::socket& socket()
  {
    return socket_;
  }

  void start()
  {
 std::cout<<"starting"<<std::endl;
  boost::asio::async_read_until(socket_, buffer, ' ',
        boost::bind(&session::handle_read, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {

 std::ostringstream ss;
 ss<<&buffer;
 std::string s = ss.str();
 std::cout<<s<<std::endl;

 if (!error)
    {
      boost::asio::async_write(socket_,
          boost::asio::buffer(s),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));
    }
    else
    {
      delete this;
    }
  }

  void handle_write(const boost::system::error_code& error)
  {
 std::cout<<"handling write"<<std::endl;
    if (!error)
    {
    }
    else
    {
      delete this;
    }
  }

private:
  tcp::socket socket_;
  boost::asio::streambuf buffer;
};

class server
{
public:
  server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
      acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
  {
    session* new_session = new session(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
      new_session = new session(io_service_);
      acceptor_.async_accept(new_session->socket(),
          boost::bind(&server::handle_accept, this, new_session,
            boost::asio::placeholders::error));
    }
    else
    {
      delete new_session;
    }
  }

private:
  boost::asio::io_service& io_service_;
  tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_tcp_echo_server <port>\n";
      return 1;
    }

    boost::asio::io_service io_service;

    using namespace std; // For atoi.
    server s(io_service, atoi(argv[1]));

    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}
like image 578
user368831 Avatar asked Jun 17 '10 02:06

user368831


3 Answers

The answer by Cubbi is correct: async_read_until() might read extra data into the buffer.

The other answer by ecoretchi is slightly dangerous. It is not incorrect, but only works work this specific question, which reads input word by word (until ' ').

If you are reading input line by line (until '\n'), you will lose data with that solution. This happens because if you use commit(), and >> operator splits your data at something else than newline (for example, you have numbers and whitespaces in the data) all subsequent data is lost.

When you are reading line by line, you should to use getline() to get one line from the buffer. This leaves rest of the data waiting in the streambuf class and allows you to read it with subsequent calls. This is also the reason you must have the streambuf as a member variable, not as a local variable in a function; if it was a local variable, any extra data beyond the first line would be lost.

std::istream is(&buffer);
std::string result_line;
std::getline(is, result_line);
like image 103
VLL Avatar answered Oct 18 '22 03:10

VLL


Read the description of async_read_until carefully. It says:

After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter.

What this means in your case is that inside handle_read(), you should only access the first bytes_transferred bytes from the buffer. As of now, your bytes_transferred parameter is unused.

like image 31
Cubbi Avatar answered Oct 18 '22 05:10

Cubbi


This is by design. Asio does not have good place to store excess data, so it handles them to you. Asio is not parser library, take a look at boost::spirit, or do parsing ad hoc, like our ancestors. :)

like image 1
Hrissan Avatar answered Oct 18 '22 03:10

Hrissan