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;
}
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);
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.
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. :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With