Boost::Asio C++ Why I`m getting the garbage while reading buffer? - c++

I have a client which read file and sends the data to server line by line. Server must count the number of lines sent. I'm using boost::asio::async_read_until to reach this result. But I'm getting the garbage (like this: Line: �68�) when trying to read data from buffer. Client sends the data only in ASCII encoding.
Client code fragment:
std::ifstream infile(argv[1]);
std::string line;
while (std::getline(infile, line)) {
boost::asio::write(s, boost::asio::buffer(line + '\n'));
std::cout << line << std::endl;
Server read function:
void handle_read(const boost::system::error_code& error, size_t bytes_trasferred, boost::asio::streambuf& buf)
{
if (!error)
{
if (!bytes_trasferred)
{
std::cout << "0 bytes trasferred\n";
return;
}
std::string data = boost::asio::buffer_cast<const char*>(buf.data());
std::istringstream is(data);
std::string line;
std::getline(is, line);
std::cout << "Line: " << line << std::endl;
}
else
std::cerr << "Error: " << error.message() << std::endl;
}
void do_read()
{
std::cout << "do_read\n";
auto self(shared_from_this());
boost::asio::streambuf buf;
buf.prepare(1048576);
std::cout << "ASYNC\n";
boost::asio::async_read_until(socket_, buf, "\n",
boost::bind(&TcpConnection::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(buf)));
}
How to resolve it? Help me, please!
I've tried to use 'self' 'this' instead. It follows to memory leak.
I've tried to add null terminator after getting data in handler function. ChatGPT said me about this way, but behavior still same.

Here:
std::string data = boost::asio::buffer_cast<const char*>(buf.data());
This is Undefined Behaviour as your are assuming that buf.data() points to a character sequence that is properly terminated with a NUL character, where you have absolutely no reason to assume this.
Besides you have UB because you pass a reference to buf, a local variable, which will by definition no longer be valid after do_read returns.
Thirdly, as Alan pointed out, you failed to copy the shared pointer (self) into the bound handler.
You're mixing many different ways to attack the problem of dealing with line-wise input. By far the easiest approach is to
use a dynamic buffer (like indeed, streambuf)
make it a member, because that's why you have shared_from_this in the first place
do not use prepare() because async_read_until knows how to do that (just like it does commit() for you)
do use consume() instead of doing getline /again/, even though async_read_until already tells you where the newline is.
Combining that:
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
do_read();
}
private:
tcp::socket socket_;
asio::streambuf buf;
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
std::string const line(boost::asio::buffer_cast<char const*>(buf.data()), n);
buf.consume(n);
std::cout << "Line: " << quoted(line) << std::endl;
do_read();
}
}
void do_read() {
async_read_until( //
socket_, buf, "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
};
I'd probably simplify, avoiding copying and potentially allocations:
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
buf_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string buf_;
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
auto line = std::string_view(buf_).substr(0, n);
std::cout << "Line: " << quoted(line) << std::endl;
buf_.erase(0, n);
do_read();
}
}
void do_read() {
async_read_until( //
socket_, asio::dynamic_buffer(buf_), "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
};
This still uses a dynamic buffer, but you don't have to do the extra hoops with istream which you apparently really don't need. For illustration purposes only, you could make the dynamic_string_buffer explicit:
void run() {
backing_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string backing_;
asio::dynamic_string_buffer<char, std::char_traits<char>, std::allocator<char>> buf_{backing_};
void handle_read(error_code ec, size_t n) {
std::cerr << "handle_read: " << ec.message() << std::endl;
if (!ec) {
auto line = std::string_view(backing_).substr(0, n);
std::cout << "Line: " << quoted(line) << std::endl;
buf_.consume(n); // look mom, it's a DynamiceBuffer all the same!
do_read();
}
}
void do_read() {
async_read_until( //
socket_, buf_, "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
Note, I wouldn't do that, but it demonstrates you how both approaches use the same DynamicBuffer concept as you did with streambuf.
Live Demo
Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
namespace ph = asio::placeholders;
using asio::ip::tcp;
using boost::system::error_code;
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
TcpConnection(tcp::socket s) : socket_(std::move(s)) {}
void run() {
buf_.reserve(8192); // optional, tune to taste
do_read();
}
private:
tcp::socket socket_;
std::string buf_;
void handle_read(error_code ec, size_t n) {
if (n) {
auto line = std::string_view(buf_).substr(0, n - 1); // exclude '\n'
std::cout << socket_.remote_endpoint() << " Line: " << quoted(line) << std::endl;
buf_.erase(0, n);
}
if (!ec)
do_read();
else
std::cerr << "handle_read: n:" << n << " " << ec.message() << std::endl;
}
void do_read() {
async_read_until( //
socket_, asio::dynamic_buffer(buf_), "\n",
boost::bind(&TcpConnection::handle_read, shared_from_this(), ph::error,
ph::bytes_transferred));
}
};
struct Server {
Server(asio::any_io_executor ex) : acc(ex, {{}, 7878}) {}
void start() {
do_accept();
}
private:
tcp::acceptor acc;
void do_accept() {
acc.async_accept(make_strand(acc.get_executor()), [this](error_code ec, tcp::socket s) {
if (!ec) {
std::make_shared<TcpConnection>(std::move(s))->run();
do_accept();
}
});
}
};
int main() {
asio::io_context ioc;
Server srv(ioc.get_executor());
srv.start();
ioc.run();
}
With the client emulated by a very similar oneliner:
netcat 127.0.0.1 7878 -w0 < main.cpp
Prints:
127.0.0.1:54860 Line: "#include <boost/asio.hpp>"
127.0.0.1:54860 Line: "#include <boost/bind/bind.hpp>"
127.0.0.1:54860 Line: "#include <iomanip>"
127.0.0.1:54860 Line: "#include <iostream>"
127.0.0.1:54860 Line: "namespace asio = boost::asio;"
...
...
127.0.0.1:54860 Line: "int main() {"
127.0.0.1:54860 Line: " asio::io_context ioc;"
127.0.0.1:54860 Line: ""
127.0.0.1:54860 Line: " Server srv(ioc.get_executor());"
127.0.0.1:54860 Line: " srv.start();"
127.0.0.1:54860 Line: ""
127.0.0.1:54860 Line: " ioc.run();"
127.0.0.1:54860 Line: "}"
handle_read: n:0 End of file
Local demo showing multiple concurrent clients:

Related

Boost ASIO performing async write/read/write handshake with a timer

I have an application where I need to connect to a socket, send a handshake message (send command1, get response, send command2), and then receive data. It is set to expire after a timeout, stop the io_service, and then attempt to reconnect. There is no error message when I do my first async_write but the following async_read waits until the timer expires, and then reconnects in an infinite loop.
My code looks like:
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <string>
#include <memory>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace std;
using boost::asio::ip::tcp;
static shared_ptr<boost::asio::io_service> _ios;
static shared_ptr<boost::asio::deadline_timer> timer;
static shared_ptr<boost::asio::ip::tcp::socket> tcp_sock;
static shared_ptr<tcp::resolver> _resolver;
static boost::asio::ip::tcp::resolver::results_type eps;
string buffer(1024,0);
void handle_read(const boost::system::error_code& ec, size_t bytes)
{
if (ec)
{
cout << "error: " << ec.message() << endl;
_ios->stop();
return;
}
// got first response, send off reply
if (buffer == "response")
{
boost::asio::async_write(*tcp_sock, boost::asio::buffer("command2",7),
[](auto ec, auto bytes)
{
if (ec)
{
cout << "write error: " << ec.message() << endl;
_ios->stop();
return;
}
});
}
else
{
// parse incoming data
}
// attempt next read
timer->expires_from_now(boost::posix_time::seconds(10));
boost::asio::async_read(*tcp_sock, boost::asio::buffer(buffer,buffer.size()), handle_read);
}
void get_response()
{
timer->expires_from_now(boost::posix_time::seconds(10));
boost::asio::async_read(*tcp_sock, boost::asio::buffer(buffer,buffer.size()), handle_read);
}
void on_connected(const boost::system::error_code& ec, tcp::endpoint)
{
if (!tcp_sock->is_open())
{
cout << "socket is not open" << endl;
_ios->stop();
}
else if (ec)
{
cout << "error: " << ec.message() << endl;
_ios->stop();
return;
}
else
{
cout << "connected" << endl;
// do handshake (no errors?)
boost::asio::async_write(*tcp_sock, boost::asio::buffer("command1",7),
[](auto ec, auto bytes)
{
if (ec)
{
cout << "write error: " << ec.message() << endl;
_ios->stop();
return;
}
get_response();
});
}
}
void check_timer()
{
if (timer->expires_at() <= boost::asio::deadline_timer::traits_type::now())
{
tcp_sock->close();
timer->expires_at(boost::posix_time::pos_infin);
}
timer->async_wait(boost::bind(check_deadline));
}
void init(string ip, string port)
{
// set/reset data and connect
_resolver.reset(new tcp::resolver(*_ios));
eps = _resolver->resolve(ip, port);
timer.reset(new boost::asio::deadline_timer(*_ios));
tcp_sock.reset(new boost::asio::ip::tcp::socket(*_ios));
timer->expires_from_now(boost::posix_time::seconds(5));
// start async connect
boost::asio::async_connect(*tcp_sock, eps, on_connected);
timer->async_wait(boost::bind(check_timer));
}
int main(int argc, char** argv)
{
while (1)
{
// start new io context
_ios.reset(new boost::asio::io_service);
init(argv[1],argv[2]);
_ios->run();
cout << "try reconnect" << endl;
}
return 0;
}
Why would I be timing out? When I do a netcat and follow the same procedure things look ok. I get no errors from the async_write indicating that there are any errors and I am making sure to not call the async_read for the response until I am in the write handler.
Others have been spot on. You use "blanket" read, which means it only completes at error (like EOF) or when the buffer is full (docs)
Besides your code is over-complicated (excess dynamic allocation, manual new, globals, etc).
The following simplified/cleaned up version still exhibits your problem: http://coliru.stacked-crooked.com/a/8f5d0820b3cee186
Since it looks like you just want to limit over-all time of the request, I'd suggest dropping the timer and just limit the time to run the io_context.
Also showing how to use '\n' for message delimiter and avoid manually managing dynamic buffers:
Live On Coliru
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
using namespace std::literals;
struct Client {
#define HANDLE(memfun) std::bind(&Client::memfun, this, std::placeholders::_1, std::placeholders::_2)
Client(std::string const& ip, std::string const& port) {
async_connect(_sock, tcp::resolver{_ios}.resolve(ip, port), HANDLE(on_connected));
}
void run() { _ios.run_for(10s); }
private:
asio::io_service _ios;
asio::ip::tcp::socket _sock{_ios};
std::string _buffer;
void on_connected(error_code ec, tcp::endpoint) {
std::cout << "on_connected: " << ec.message() << std::endl;
if (ec)
return;
async_write(_sock, asio::buffer("command1\n"sv), [this](error_code ec, size_t) {
std::cout << "write: " << ec.message() << std::endl;
if (!ec)
get_response();
});
}
void get_response() {
async_read_until(_sock, asio::dynamic_buffer(_buffer /*, 1024*/), "\n", HANDLE(on_read));
}
void on_read(error_code ec, size_t bytes) {
std::cout << "handle_read: " << ec.message() << " " << bytes << std::endl;
if (ec)
return;
auto cmd = _buffer.substr(0, bytes);
_buffer.erase(0, bytes);
// got first response, send off reply
std::cout << "Handling command " << quoted(cmd) << std::endl;
if (cmd == "response\n") {
async_write(_sock, asio::buffer("command2\n"sv), [](error_code ec, size_t) {
std::cout << "write2: " << ec.message() << std::endl;
});
} else {
// TODO parse cmd
}
get_response(); // attempt next read
}
};
int main(int argc, char** argv) {
assert(argc == 3);
while (1) {
Client(argv[1], argv[2]).run();
std::this_thread::sleep_for(1s); // for demo on COLIRU
std::cout << "try reconnect" << std::endl;
}
}
With output live on coliru:
on_connected: Connection refused
try reconnect
on_connected: Success
write: Success
command1
handle_read: Success 4
Handling command "one
"
handle_read: Success 9
Handling command "response
"
write2: Success
command2
handle_read: Success 6
Handling command "three
"
handle_read: End of file 0
try reconnect
on_connected: Success
write: Success
command1
Local interactive demo:
Sidenote: as long as resolve() isn't happening asynchronously it will not be subject to the timeouts.

ASIO (Boost) SSL Network Transfer

I have this piece of code that I've put together from various Boost sources to try and create something that will connect to a host and download the root page/file via SSL. The program runs but returns an empty reply. Could someone suggest why this is the case and suggest an improvement? Link with -l pthread -l ssl -l crypto.
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <asio.hpp>
#include <asio/ssl.hpp>
#include <functional>
static const char *HOSTNAME { "google.com" };
static const char *HOSTPORT { "https" };
std::string file { "/" };
std::string request {
"GET " + file + " "
+ "HTTP/1.1\r\nHost: " + std::string(HOSTNAME) + "\r\n\r\n" };
enum { max_length = 1024 };
class Client
{
asio::ssl::stream<asio::ip::tcp::socket> ssock;
char reply[max_length];
std::size_t bytes_transferred;
std::error_code ec;
public:
Client(asio::io_service &io_service, asio::ssl::context &context, asio::ip::tcp::resolver::iterator endpoint_iterator) :
ssock(io_service, context)
{
auto handle_connect { std::bind(&Client::handle_handshake, this, ec) };
asio::async_connect(ssock.lowest_layer(), endpoint_iterator, handle_connect);
}
void handle_connect(const std::error_code &ec)
{
if (!ec)
{
auto handle_handshake { std::bind(&Client::handle_handshake, this, ec) };
ssock.async_handshake(asio::ssl::stream_base::client, handle_handshake);
}
else std::cout << "Connect failed: " << ec.message() << "\n";
}
void handle_handshake(const std::error_code &ec)
{
if (!ec)
{
std::cout << request << std::endl;
auto handle_write { std::bind(&Client::handle_write, this, ec, bytes_transferred) };
asio::async_write(ssock, asio::buffer(request), handle_write);
}
else std::cout << "Handshake failed: " << ec.message() << "\n";
}
void handle_write(const std::error_code &ec, std::size_t bytes_transferred)
{
if (!ec)
{
auto handle_read { std::bind(&Client::handle_read, this, ec, bytes_transferred) };
asio::async_read(ssock, asio::buffer(reply, bytes_transferred), handle_read);
}
else std::cout << "Write failed: " << ec.message() << "\n";
}
void handle_read(const std::error_code &ec, std::size_t bytes_transferred)
{
if (!ec)
{
std::cout << "Reply: ";
std::cout.write(reply, bytes_transferred);
std::cout << "\n";
}
else std::cout << "Read failed: " << ec.message() << "\n";
}
};
int main(int argc, char* argv[])
{
try
{
asio::io_service io_service;
asio::ip::tcp::resolver resolver(io_service);
asio::ip::tcp::resolver::query query(HOSTNAME, HOSTPORT);
asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
asio::ssl::context ctx(asio::ssl::context::sslv23);
Client client(io_service, ctx, iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I think the problem is with these callbacks. The operations within the callbacks themselves are correct as they're taken straight from the example. Is there a better way to rewrite by using Lambdas for callbacks? I don't like the way these callbacks are bound by bind.
auto handle_connect { std::bind(&Client::handle_handshake, this, ec) };
in the constructor should be:
auto handle_connect { std::bind(&Client::handle_connect, this, ec) };

Boost asio, async_read and acync_write not calling callbacks

I'm encapsulating the boost-asio socket, but I got an issue with it, but neither async_read nor async_write calls their callback function and I don't understand why.
I've tried using async_read_some but had the same issue.
Here's the code I've written so far
#include <iostream>
#include "socket.hpp"
Socket::Socket()
{
boost::asio::ip::tcp::endpoint ep_tmp(boost::asio::ip::tcp::v4(), 4242);
endpoint = ep_tmp;
acceptor = new boost::asio::ip::tcp::acceptor(ios, endpoint);
tcp_socket = new boost::asio::ip::tcp::socket(ios);
acceptor->listen();
}
Socket::~Socket()
{
delete(acceptor);
delete(tcp_socket);
}
void Socket::get_connection()
{
acceptor->async_accept(*tcp_socket, [](const boost::system::error_code &ec)
{
std::cout << "Connection received." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
});
this->exec();
}
void Socket::send(std::string &message)
{
async_write(*tcp_socket, boost::asio::buffer(message),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Sending datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred << " bytes transferred." << std::endl;
});
}
void Socket::receive(void)
{
char *buf;
buf = (char *)malloc(sizeof(char) * 50);
buf = (char *)memset(buf, 0, 50);
async_read(*tcp_socket, boost::asio::buffer(buf, 50),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Receiving datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred
<< " bytes transferred." << std::endl;
});
}
void Socket::exec(void)
{
ios.run();
}
int main()
{
Socket serv;
std::string data_test;
data_test = "Test\n";
serv.get_connection();
serv.send(data_test);
serv.exec();
serv.receive();
serv.exec();
return (0);
}
The malloc bit is temporary until I find a way to do it without using C.
I'd be really thankful if someone could enlighten me on that issue
You have to call io_service::reset before second and later calls to io_service::run. And you probably want to use synchronous API instead, as your current approach absolutely defeats the purpose of asynchronicity.
I'm with yuri: prefer non-async unless you know what you're doing.
It could look like this: http://coliru.stacked-crooked.com/a/523a7828a9aee4b2
#include <boost/asio.hpp>
#include <iostream>
namespace ba = boost::asio;
using ba::ip::tcp;
class Socket {
public:
Socket() { acceptor.listen(); }
void get_connection();
void exec();
void send(std::string const &message);
void receive(void);
private:
ba::io_service ios;
tcp::endpoint endpoint{ tcp::v4(), 4242 };
tcp::acceptor acceptor{ ios, endpoint };
tcp::socket tcp_socket{ ios };
};
void Socket::get_connection() {
acceptor.accept(tcp_socket);
std::cout << "Connection received.\n";
}
void Socket::send(std::string const &message) {
std::cout << "Sending datas.\n";
auto bytes_transferred = ba::write(tcp_socket, ba::buffer(message));
std::cout << bytes_transferred << " bytes transferred.\n";
}
void Socket::receive(void) {
std::cout << "Receiving datas.\n";
char buf[50] = { 0 };
auto bytes_transferred = ba::read(tcp_socket, ba::buffer(buf));
std::cout << bytes_transferred << " bytes transferred.\n";
}
int main() {
Socket serv;
serv.get_connection();
serv.send("Test\n");
serv.receive();
}
If you want async behaviour, you have to manage the lifetimes of each buffer/connection-specific resource. There are many examples of that, e.g. in the docs or here: http://coliru.stacked-crooked.com/a/95e2000e49b4db1d
On the perils of buffer lifetime: client server simple example nonblocking

How to access boost::asio::streambuf input as a string?

I am writing a very simple HTTP server based on: http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp
I have tried numerous techniques to extract the data from a boost::asio::streambuf in order to parse HTTP headers. The streambuf object does not appear to manage it's memory properly (or, more likely, I am mis-using it) & I end up getting a seg fault.
As you can see from the code, none of the techniques suggested here or here work. I suspect this is because I'm using boost::asio::async_read_until() to read all the headers, rather than just a single header at a time as most other coders appear to be doing.
Any advice/pointers would be appreciated.
/*
g++ -D TRY1 -ggdb3 -I $e/boost-1.62/include /tmp/streambuf.bug.cc $e/boost-1.62/lib/libboost_system.a -D TRY1
or
g++ -D TRY2 -ggdb3 -I $e/boost-1.62/include /tmp/streambuf.bug.cc $e/boost-1.62/lib/libboost_system.a -D TRY2
or
g++ -D TRY3 -ggdb3 -I $e/boost-1.62/include /tmp/streambuf.bug.cc $e/boost-1.62/lib/libboost_system.a -D TRY3
*/
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket) : socket_(std::move(socket)), dbg(true) {
assert(headers.empty());
memset(padding, 0, 10000);
std::cout << "buffer size: " << buffer.max_size() << '\n';
}
void start() { readHeaders(); }
private:
void readHeaders() {
if (dbg)
std::cout << "readHeaders() start\n";
//auto self(shared_from_this());
boost::asio::async_read_until(socket_, buffer, "\r\n\r\n", [this](const boost::system::error_code &ec, std::size_t length) {
if (dbg)
std::cout << "Read " << length << " bytes of headers in async_read_until()\n";
if (ec)
throw std::runtime_error("Error code in readHeaders()");
#ifdef TRY1
std::istream stream(&buffer);
std::string str;
assert(!corrupted("A1"));
while (std::getline(stream, str)) { // seg fault! (on 3rd invocation)
assert(!corrupted("A2"));
std::cout << "str=" << str << '\n';
assert(!corrupted("A3"));
}
#endif
#if 0
std::string str;
boost::asio::buffer_copy(boost::asio::buffer(str), buffer); // ugh, won't compile
#endif
#if 0
std::vector<unsigned char> v(buffer.size());
boost::asio::buffer_copy(boost::asio::buffer(v), buffer); // ugh, won't compile
const std::string str(v.begin(), v.end());
#endif
#ifdef TRY2
std::string str;
auto data = buffer.data();
assert(!corrupted("B1"));
for (auto it = data.begin(); it != data.end(); ++it) {
const auto buf = *it;
std::cout << "buf_size=" << boost::asio::buffer_size(buf) << '\n';
assert(!corrupted("B2"));
const char *tmp = boost::asio::buffer_cast<const char *>(buf);
assert(!corrupted("B3"));
str.append(tmp); // BUG!!
assert(!corrupted("B4")); // fails
}
#endif
#ifdef TRY3
auto data = buffer.data();
auto end = data.end();
std::string str;
assert(!corrupted("C1"));
for (auto it = data.begin(); it != end; ++it) {
assert(!corrupted("C2"));
std::vector<unsigned char> v(boost::asio::buffer_size(*it));
assert(!corrupted("C3"));
boost::asio::buffer_copy(boost::asio::buffer(v), *it); // BUG!!
assert(!corrupted("C4")); // fails
str.append(v.begin(), v.end());
assert(!corrupted("C5"));
}
#endif
#ifdef TRY4
assert(!corrupted("D1"));
const std::string str(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data())); // BUG!!
assert(!corrupted("D2")); // fails
#endif
#ifdef TRY5
assert(!corrupted("E1"));
const std::string str((std::istreambuf_iterator<char>(&buffer)), std::istreambuf_iterator<char>()); // seg faults!
assert(!corrupted("E2"));
#endif
#ifdef TRY6
boost::asio::streambuf::const_buffers_type bufs = buffer.data();
assert(!corrupted("F1"));
std::string str(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + buffer.size()); // BUG!!
assert(!corrupted("F2")); // fails
#endif
assert(!corrupted("Z1"));
std::cout << "str=" << str << "end of data\n";
std::istringstream input(str);
std::string line;
while (std::getline(input, line)) {
assert(!corrupted("Z2"));
if (line.size() == 1)
continue; // blank line
if (line.substr(0, 3) == "GET")
continue; // TODO: handle properly
const auto idx = line.find(':');
assert(idx != std::string::npos);
const std::string key(line.begin(), line.begin() + idx);
const std::string val(line.begin() + idx + 2, line.end());
// std::cout << "key=" << key << " val=" << val << '\n';
assert(!corrupted("Z3"));
headers[key] = val;
assert(!corrupted("Z4"));
}
assert(!corrupted("Z5"));
for (auto it3 : headers) {
std::cout << it3.first << '=' << it3.second << '\n';
}
const auto it2 = headers.find("Content Length");
contentLength = (it2 == headers.end() ? 0 : atoi(it2->second.c_str()));
if (contentLength > 0) {
const boost::system::error_code ec; // (boost::system::errc::success);
readBody(ec);
}
});
if (dbg)
std::cout << "readHeaders() end\n";
}
void readBody (const boost::system::error_code &ec) {
if (dbg)
std::cout << "readBody()\n";
if (ec)
throw std::runtime_error("Error code in readBody()");
boost::asio::streambuf::const_buffers_type bufs = buffer.data();
body.append(boost::asio::buffers_begin(bufs), boost::asio::buffers_begin(bufs) + buffer.size());
if (dbg)
std::cout << "body.size=" << body.size() << " content length=" << contentLength << '\n';
boost::asio::async_read(socket_,
buffer,
boost::asio::transfer_at_least(1),
boost::bind(&session::readBody, this, boost::asio::placeholders::error));
}
bool corrupted (const std::string s) const {
bool b = false;
if (strlen(padding) > 0) {
std::cout << "buffer overflow detected # " << s << "! padding is: " << padding << '\n';
std::cout.flush();
b = true;
}
if (headers.size() > 1000) {
std::cout << headers.size() << " headers!!\n";
b = true;
}
return b;
}
tcp::socket socket_;
boost::asio::streambuf buffer;
char padding[10000]; // $buffer appears not to manage it's memory properly. Add some padding to detect overflows.
std::map<std::string, std::string> headers;
uint contentLength;
std::string body;
const bool dbg;
};
class server
{
public:
server(boost::asio::io_service& io_service, short port) : acceptor_(io_service, tcp::endpoint(tcp::v4(), port)), socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
if (!ec) {
std::cout << "Connection accepted\n";
std::make_shared<session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
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;
server s(io_service, std::atoi(argv[1]));
io_service.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I am using boost v1.62, gcc v6.1 on Linux (Ubuntu 12.04).
You can read from the streambuf
manually (see documentation)
using std::istream:
boost::asio::streambuf buf;
std::istream is(&buf);
// usual extraction:
int i;
if (is >> i) {
// use `i`
}
// or usual line-wise extraction:
std::string line;
while (std::getline(is, line)) {
// do something with `line`
}
alternative use boost::asio::buffer_* functions (buffer_begin(), buffer_end() and buffer_copy) - How copy or reuse boost::asio::streambuf?
Copying the contents of a boost::asio::streambuf as a string has been answered in both the Copy a streambuf's contents to a string and How copy or reuse boost::asio::streambuf questions:
boost::asio::streambuf source;
...
std::string target{buffers_begin(source.data()), buffers_end(source.data())};
The problems being observed are the result of undefined behavior. The program fails to meet the lifetime requirement for async_read_until()'s b parameter, as the streambuf are being destroyed before the completion handler is invoked:
[...] Ownership of the streambuf is retained by the caller, which must guarantee that it remains valid until the handler is called.
In this case, streambuf is a data member of session, and session objects are managed by a shared pointer. The only shared pointer managing session is both created and destroyed in the following expression:
std::make_shared<session>(std::move(socket_))->start();
Within start(), an async_read_until() operation is initiated. However, upon returning form start(), the session's buffer is destroyed before the async_read_until()'s completion handler is invoked, violating the lifetime requirement.
The idiomatic solution used by the official Asio examples is to capture the results of shared_from_this() in the completion handler's lambda capture. This guarantees that the lifetime of the session will be at least as long as the completion handler.
auto self(shared_from_this());
async_read_until(socket_, buffer_, ...,
[this, self](boost::system::error_code& ec, std::size_t length)
{
// `self` keeps the `session` alive for the lifetime of the
// handler. If more async operations are initiated from within
// this handler, then the completion handlers should capture
// `self` as well.
...
});
The exact answer to your question gave sehe. Below is some pseudocode I am using currently for parsing headers.
// This is where to store headers.
_STL::map<_STL::string, _STL::string> m_headers;
// buffer is of type asio::streambuf and contains response from asio::async_read_until
_STL::istream response_stream_headers(&buffer);
// Some helper variables.
_STL::string header, header_name, header_value;
while (true) {
_STL::getline(response_stream_headers, header, '\r');
// Remove \n symbol from the stream.
response_stream_headers.get();
if (header == "") {
// We reached end of headers, there might be still some more data!!
break;
}
// Parse header to key->value
size_t separator_pos = header.find(':');
if (separator_pos != _STL::string::npos) {
header_name = header.substr(0, separator_pos);
if (separator_pos < header.length() - 1) {
header_value = header.substr(separator_pos + 1);
}
else {
header_value = "";
}
boost::trim_left(header_value);
m_headers[name] = value;
}
}
// Parsing is done, but some of the request response could have been reed by
// asio::async_read_until, so whe read response_stream_headers untill end.
// You should use body_response_start as the begining of your response.
std::string body_response_start(std::istreambuf_iterator<char>(response_stream_headers), {});

Asio: Prevent asynchronous client from being deleted?

I have the following code, trying to code an asynchronous client.
The problem is that in main(), the Client gets deleted in the try-catch block, because execution leaves the scope.
I've tried to come up with a solution to this problem, like adding a while(true), but I don't like this approach. Also, I don't prefer a getchar().
Due to the asynchronous nature of the calls, both connect() and loop() returns immediately.
How can I fix this?
#include <iostream>
#include <thread>
#include <string>
#include <boost\asio.hpp>
#include <Windows.h>
#define DELIM "\r\n"
using namespace boost;
class Client {
public:
Client(const std::string& raw_ip_address, unsigned short port_num) :
m_ep(asio::ip::address::from_string(raw_ip_address), port_num), m_sock(m_ios)
{
m_work.reset(new asio::io_service::work(m_ios));
m_thread.reset(new std::thread([this]() {
m_ios.run();
}));
m_sock.open(m_ep.protocol());
}
void connect()
{
m_sock.async_connect(m_ep, [this](const system::error_code& ec)
{
if (ec != 0) {
std::cout << "async_connect() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::cout << "Connection to server has been established." << std::endl;
});
}
void loop()
{
std::thread t = std::thread([this]()
{
recv();
});
t.join();
}
void recv()
{
asio::async_read_until(m_sock, buf, DELIM, [this](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_read_until() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::istream is(&buf);
std::string req;
std::getline(is, req, '\r');
is.get(); // discard newline
std::cout << "Received: " << req << std::endl;
if (req == "alive") {
recv();
}
else if (req == "close") {
close();
return;
}
else {
send(req + DELIM);
}
});
}
void send(std::string resp)
{
auto s = std::make_shared<std::string>(resp);
asio::async_write(m_sock, asio::buffer(*s), [this, s](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_write() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
else {
recv();
}
});
}
void close()
{
m_sock.close();
m_work.reset();
m_thread->join();
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
std::unique_ptr<asio::io_service::work> m_work;
std::unique_ptr<std::thread> m_thread;
asio::streambuf buf;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 8001;
try {
Client client(raw_ip_address, port_num);
client.connect();
client.loop();
}
catch (system::system_error &err) {
std::cout << "main() error: " << err.what() << " (" << err.code() << ") " << std::endl;
return err.code().value();
}
return 0;
}
You've not really understood how asio works. Typically in the main thread(s) you will call io_service::run() (which will handle all the asynchronous events.)
To ensure the lifetime of the Client, use a shared_ptr<> and ensure this shared pointer is used in the handlers. For example..
io_service service;
{
// Create the client - outside of this scope, asio will manage
// the life time of the client
auto client = make_shared<Client>(service);
client->connect(); // setup the connect operation..
}
// Now run the io service event loop - this will block until there are no more
// events to handle
service.run();
Now you need to refactor your Client code:
class Client : public std::enable_shared_from_this<Client> {
Client(io_service& service): socket_(service) ...
{ }
void connect() {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
socket_.async_connect(endpoint_, [self = this->shared_from_this()](auto ec)
{
if (ec) {
return;
}
// Read
self->read(self);
});
}
void read(shared_ptr<Client> self) {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
asio::async_read_until(socket_, buffer_, DELIM, [self](auto ec, auto size)
{
if (ec) {
return;
}
// Handle the data
// Setup the next read operation
self->read(self)
});
}
};
You have a thread for the read operation - which is not necessary. That will register one async read operation and return immediately. You need to register a new read operation to continue reading the socket (as I've sketched out..)
You can post any function to io_service via post(Handler)
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/io_service/post.html
Then in the main() do something like:
while (!exit) {
io_service.run_one();
}
Or call io_service::run_one or io_service::run in the main()