Boost asio getting `Error Code : 125 Operation cancelled` [closed] - c++

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I was just trying to accept incoming request with acceptor socket and when it enters the async_accept it throws out an error, I'm not sure what is causing the error. The thing is I'm not even sending the request from the client but still it enters the async_accept handler for some reason, here is the part of the code which is causing the error
main.cpp
#include <iostream>
#include "server.hpp"
#include "connection.hpp"
class Connection;
int main(){
using Server_ = Server<Connection>;
auto server = std::make_unique<Server_>(8989);
server->start();
}
server.hpp
#pragma once
#include <thread>
#include <boost/asio.hpp>
template <typename Connection>
class Server{
using shared_connection = std::shared_ptr<Connection>;
private:
unsigned short port_;
std::thread io_thread_;
boost::asio::io_context ioc_;
boost::asio::io_context::work work_;
boost::asio::ip::tcp::endpoint endpoint_;
boost::asio::ip::tcp::acceptor acceptor_;
void handle_new_request(shared_connection connection, const system::error_code &ec){
if(!ec){
connection->start_operation();
}else{
error::print(ec);
return;
}
}
public:
explicit Server(unsigned short port)
: acceptor_(ioc_)
, work_(ioc_)
, port_(port)
, endpoint_(asio::ip::tcp::v4(), port) {
io_thread_ = std::move(std::thread([&]{ ioc_.run(); }));
io_thread_.join();
}
~Server() {
if(acceptor_.is_open())
acceptor_.close();
io_thread_.join();
}
void start(){
using namespace asio::ip;
system::error_code ec;
// creates an actual operating system socket
acceptor_.open(endpoint_.protocol(),ec);
acceptor_.set_option(tcp::acceptor::reuse_address(true),ec);
// binds to the endpoint
acceptor_.bind(endpoint_,ec);
if(!ec){
std::cout << "Listening for requests from port " << port_ << std::endl;
acceptor_.listen();
}else{
error::print(ec);
return;
}
shared_connection connection = std::make_shared<Connection>(ioc_);
acceptor_.async_accept(connection->sock_,[=](system::error_code ec){
handle_new_request(connection,ec);
});
}
};
connection.hpp
#pragma once
#include <memory>
#include <boost/asio.hpp>
class Connection : public std::enable_shared_from_this<Connection> {
using shared_connection = std::shared_ptr<Connection>;
std::vector<char> buffer_space_;
private:
boost::asio::mutable_buffers_1 buffer_;
boost::asio::io_context& ioc_;
public:
boost::asio::ip::tcp::socket sock_;
explicit Connection(boost::asio::io_context &context,const int &size = 1024)
: ioc_(context)
, sock_(context)
, buffer_space_(size)
, buffer_(buffer_space_.data(),size){}
~Connection(){ if(sock_.is_open()) sock_.close(); }
void start_operation(){
if(sock_.is_open()){
sock_.async_read_some(buffer_,[me = shared_from_this()](const system::error_code &ec, std::size_t bytes){
if(!ec){
for(int i=0;i<bytes;++i)
std::cout << me->buffer_space_[i];
std::cout << std::endl;
me->start_operation();
}else{
error::print(ec);
return;
}
});
}
}
};
error.hpp
#pragma once
#include <iostream>
#include <boost/system/error_code.hpp>
namespace error {
inline void print(const boost::system::error_code &ec){
std::cerr << "Error Code : " << ec.value() << ", Message : " << ec.message() << std::endl;
}
}
Any help on this would be appreciated. Thanks!
The error lies in having the io_thread_.run() in the destructor which would destroy the socket object by then

int main(){
using Server_ = Server<Connection>;
auto server = std::make_unique<Server_>(8989);
server->start();
}
server will call the deleter, which destroys Server_ on exiting main. You Would like to join any threads or await the shutdown of the server before exiting main.
You would in your case join the iothread, like you TRIED to do.
However, you do it in the server constructor:
explicit Server(unsigned short port)
: acceptor_(ioc_)
, work_(ioc_)
, port_(port)
, endpoint_(asio::ip::tcp::v4(), port)
{
io_thread_ = std::move(std::thread([&] { ioc_.run(); }));
io_thread_.join();
}
I can't really figure out how this would not hang indefinitely due to the work_. On tangentially related observation is that work_ is NOT initilaized before ioc_ (or thread_) because initialization happens in order of member declaration instead of order in which the initializers appear. You will want to fix the declaration order regardless:
boost::asio::io_context ioc_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::io_context::work work_;
unsigned short port_;
boost::asio::ip::tcp::endpoint endpoint_;
std::thread io_thread_;
Similarly in Connection:
private:
boost::asio::io_context& ioc_;
public:
boost::asio::ip::tcp::socket sock_;
private:
std::vector<char> buffer_space_;
boost::asio::mutable_buffers_1 buffer_;
Fixed Demo
Live On Coliru
#include <iostream>
#include <boost/system/error_code.hpp>
namespace error {
inline void print(const boost::system::error_code& ec)
{
std::cerr << "Error Code : " << ec.value()
<< ", Message : " << ec.message() << std::endl;
}
}
#include <memory>
#include <boost/asio.hpp>
class Connection : public std::enable_shared_from_this<Connection> {
using shared_connection = std::shared_ptr<Connection>;
private:
boost::asio::io_context& ioc_;
public:
boost::asio::ip::tcp::socket sock_;
private:
std::vector<char> buffer_space_;
boost::asio::mutable_buffers_1 buffer_;
public:
explicit Connection(
boost::asio::io_context& context, const int& size = 1024)
: ioc_(context)
, sock_(context)
, buffer_space_(size)
, buffer_(buffer_space_.data(), size)
{
}
void start_operation()
{
if (sock_.is_open()) {
sock_.async_read_some(buffer_,
[me = shared_from_this()](
const boost::system::error_code& ec, std::size_t bytes) {
if (!ec) {
for (size_t i = 0; i < bytes; ++i) {
std::cout << me->buffer_space_[i];
}
std::cout << std::endl;
me->start_operation();
} else {
error::print(ec);
return;
}
});
}
}
};
#include <thread>
#include <boost/asio.hpp>
template <typename Connection> class Server {
using shared_connection = std::shared_ptr<Connection>;
private:
boost::asio::io_context ioc_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
work_ {ioc_.get_executor()};
uint16_t port_;
boost::asio::ip::tcp::endpoint endpoint_;
std::thread io_thread_;
void handle_new_request(
shared_connection connection, const boost::system::error_code& ec)
{
if (!ec) {
connection->start_operation();
} else {
error::print(ec);
return;
}
}
public:
explicit Server(uint16_t port)
: acceptor_(ioc_)
, port_(port)
, endpoint_(boost::asio::ip::tcp::v4(), port)
, io_thread_([&] { ioc_.run(); })
{ ; }
~Server()
{
if (acceptor_.is_open()) {
boost::system::error_code ec;
acceptor_.cancel(ec);
//acceptor_.close(ec);
}
work_.reset();
io_thread_.join();
}
void start()
{
using boost::asio::ip::tcp;
boost::system::error_code ec;
// creates an actual operating system socket
acceptor_.open(endpoint_.protocol(), ec);
acceptor_.set_option(tcp::acceptor::reuse_address(true), ec);
// binds to the endpoint
acceptor_.bind(endpoint_, ec);
if (!ec) {
std::cout << "Listening for requests from port " << port_
<< std::endl;
acceptor_.listen();
} else {
error::print(ec);
return;
}
shared_connection connection = std::make_shared<Connection>(ioc_);
acceptor_.async_accept(
connection->sock_, [=, this](boost::system::error_code ec) {
handle_new_request(connection, ec);
});
}
};
#include <iostream>
//#include "server.hpp"
//#include "connection.hpp"
using namespace std::chrono_literals;
class Connection;
int main()
{
using Server_ = Server<Connection>;
auto server = std::make_unique<Server_>(8989);
server->start();
std::this_thread::sleep_for(4s);
// destructor joins
}
This would give 4s for the first client to connect, and will shut down as soon as all connections are done.

Related

boost::asio::ip::tcp::acceptor terminates application when receiving connection request using async_accept

I want to make this simple server that listens to incoming connection requests, makes connections and sends some data. When I start this acceptor looks like it's working fine, it waits for those incoming connection requests, but when my client tries to connect to this acceptor it automatically crushes. I cant even catch any exceptions with catch(...)
When I start this program it looks like this in a terminal
But when I try to connect
Client application received this kind of error code
Is there something fundamentally wrong with my my_acceptor class?
class my_acceptor{
public:
my_acceptor(asio::io_context& ios, unsigned short port_num) :
m_ios(ios),
port{port_num},
m_acceptor{ios}{}
//start accepting incoming connection requests
void Start()
{
std::cout << "Acceptor Start" << std::endl;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();
InitAccept();
}
void Stop(){}
private:
void InitAccept()
{
std::cout << "Acceptor InitAccept" << std::endl;
std::shared_ptr<asio::ip::tcp::socket> sock{new asio::ip::tcp::socket(m_ios)};
m_acceptor.async_accept(*sock.get(),
[this, sock](const boost::system::error_code& error)
{
onAccept(error, sock);
});
}
void onAccept(const boost::system::error_code& ec, std::shared_ptr<asio::ip::tcp::socket> sock)
{
std::cout << "Acceptor onAccept" << std::endl;
}
private:
unsigned short port;
asio::io_context& m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
Just in case this is the Server code that wraps my_acceptor
class Server{
public:
Server(){}
//start the server
void Start(unsigned short port_num, unsigned int thread_pool_size)
{
assert(thread_pool_size > 0);
//create specified number of threads and add them to the pool
for(unsigned int i = 0; i < thread_pool_size; ++i)
{
std::unique_ptr<std::thread> th(
new std::thread([this]()
{
m_ios.run();
}));
m_thread_pool.push_back(std::move(th));
}
//create and start acceptor
acc.reset(new my_acceptor(m_ios, port_num));
acc->Start();
}
//stop the server
void Stop()
{
work_guard.reset();
acc->Stop();
m_ios.stop();
for(auto& th : m_thread_pool)
{
th->join();
}
}
private:
asio::io_context m_ios;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_guard = boost::asio::make_work_guard(m_ios);
std::unique_ptr<my_acceptor> acc;
std::vector<std::unique_ptr<std::thread>> m_thread_pool;
};
There's a threading bug, at least. tcp::acceptor is not thread-safe and you (potentially) run multiple threads. So you will need to make the acceptor access be done from a strand.
my_acceptor(asio::io_context& ios, unsigned short port_num) :
m_ios(ios),
port{port_num},
m_acceptor{make_strand(ios)}{}
And then any operation involving it must be on that strand. E.g., the missing Stop() code should look like:
void Stop(){
post(m_acceptor.get_executor(), [this] { m_acceptor.cancel(); });
}
I leave the initial accept as-is because at that point there aren't multiple threads involved.
Likewise in Start() and Stop() you should check whether acc is null, because acc->Stop() would throw and just replacing a running acc would cause Undefined Behaviour due to deleting the instance that is still having async operations in flight.
In a sidenote, m_ios.stop() should not be necessary if you stop the running acceptor. In the future you might have to signal any client connections to stop, in order for the threads to naturally join.
Here's how I'd complete the accept loop:
void onAccept(error_code ec, std::shared_ptr<tcp::socket> sock)
{
std::cout << "Acceptor onAccept " << ec.message() << " " << sock.get() << std::endl;
if (!ec) {
InitAccept();
}
}
Note how unless the socket is canceled (or otherwise in error), we keep accepting.
I think the threading issue was likely your big problem. The result after my suggestions works:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <thread>
using namespace std::chrono_literals;
namespace asio = boost::asio;
using boost::system::error_code;
using asio::ip::tcp;
class my_acceptor {
public:
my_acceptor(asio::io_context& ios, unsigned short port_num) :
m_ios(ios),
port{port_num},
m_acceptor{make_strand(ios)}{}
//start accepting incoming connection requests
void Start()
{
std::cout << "Acceptor Start" << std::endl;
tcp::endpoint endpoint(tcp::v4(), port);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(tcp::acceptor::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();
InitAccept();
}
void Stop(){
post(m_acceptor.get_executor(), [this] { m_acceptor.cancel(); });
}
private:
void InitAccept()
{
std::cout << "Acceptor InitAccept" << std::endl;
auto sock = std::make_shared<tcp::socket>(m_ios);
m_acceptor.async_accept(*sock,
[this, sock](error_code error) { onAccept(error, sock); });
}
void onAccept(error_code ec, const std::shared_ptr<tcp::socket>& sock)
{
std::cout << "Acceptor onAccept " << ec.message() << " " << sock.get() << std::endl;
if (!ec) {
InitAccept();
}
}
private:
asio::io_context& m_ios;
unsigned short port;
tcp::acceptor m_acceptor;
};
class Server{
public:
Server() = default;
//start the server
void Start(unsigned short port_num, unsigned int thread_pool_size)
{
assert(!acc); // otherwise UB results
assert(thread_pool_size > 0);
//create specified number of threads and add them to the pool
for(unsigned int i = 0; i < thread_pool_size; ++i)
{
std::unique_ptr<std::thread> th(
new std::thread([this]() { m_ios.run(); }));
m_thread_pool.push_back(std::move(th));
}
//create and start acceptor
acc = std::make_unique<my_acceptor>(m_ios, port_num);
acc->Start();
}
//stop the server
void Stop()
{
work_guard.reset();
if (acc) {
acc->Stop();
}
//m_ios.stop();
for(auto& th : m_thread_pool) {
th->join();
}
acc.reset();
}
private:
asio::io_context m_ios;
asio::executor_work_guard<asio::io_context::executor_type>
work_guard = make_work_guard(m_ios);
std::unique_ptr<my_acceptor> acc;
std::vector<std::unique_ptr<std::thread>> m_thread_pool;
};
int main() {
Server s;
s.Start(6868, 1);
std::this_thread::sleep_for(10s);
s.Stop();
}
Testing with netcat as client:
for msg in one two three; do
sleep 1
nc 127.0.0.1 6868 <<< "$msg"
done
Prints
Acceptor Start
Acceptor InitAccept
Acceptor onAccept Success 0x1f26960
Acceptor InitAccept
Acceptor onAccept Success 0x7f59f80009d0
Acceptor InitAccept
Acceptor onAccept Success 0x7f59f8000a50
Acceptor InitAccept
Acceptor onAccept Operation canceled 0x7f59f80009d0

asio aync_send memory leak

I have next snippet:
void TcpConnection::Send(const std::vector<uint8_t>& buffer) {
std::shared_ptr<std::vector<uint8_t>> bufferCopy = std::make_shared<std::vector<uint8_t>>(buffer);
auto socket = m_socket;
m_socket->async_send(asio::buffer(bufferCopy->data(), bufferCopy->size()), [socket, bufferCopy](const boost::system::error_code& err, size_t bytesSent)
{
if (err)
{
logwarning << "clientcomms_t::sendNext encountered error: " << err.message();
// Assume that the communications path is no longer
// valid.
socket->close();
}
});
}
This code leads to memory leak. if m_socket->async_send call is commented then there is not memeory leak. I can not understand why bufferCopy is not freed after callback is dispatched. What I am doing wrong?
Windows is used.
Since you don't show any relevant code, and the code shown does not contain a strict problem, I'm going to assume from the code smells.
The smell is that you have a TcpConnection class that is not enable_shared_from_this<TcpConnection> derived. This leads me to suspect you didn't plan ahead, because there's no possible reasonable way to continue using the instance after the completion of any asynchronous operation (like the async_send).
This leads me to suspect you have a crucially simple problem, which is that your completion handler never runs. There's only one situation that could explain this, and that leads me to assume you never run() the ios_service instance
Here's the situation live:
Live On Coliru
#include <boost/asio.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
#include <iostream>
auto& logwarning = std::clog;
struct TcpConnection {
using Buffer = std::vector<uint8_t>;
void Send(Buffer const &);
TcpConnection(asio::io_service& svc) : m_socket(std::make_shared<tcp::socket>(svc)) {}
tcp::socket& socket() const { return *m_socket; }
private:
std::shared_ptr<tcp::socket> m_socket;
};
void TcpConnection::Send(Buffer const &buffer) {
auto bufferCopy = std::make_shared<Buffer>(buffer);
auto socket = m_socket;
m_socket->async_send(asio::buffer(bufferCopy->data(), bufferCopy->size()),
[socket, bufferCopy](const boost::system::error_code &err, size_t /*bytesSent*/) {
if (err) {
logwarning << "clientcomms_t::sendNext encountered error: " << err.message();
// Assume that the communications path is no longer
// valid.
socket->close();
}
});
}
int main() {
asio::io_service svc;
tcp::acceptor a(svc, tcp::v4());
a.bind({{}, 6767});
a.listen();
boost::system::error_code ec;
do {
TcpConnection conn(svc);
a.accept(conn.socket(), ec);
char const* greeting = "whale hello there!\n";
conn.Send({greeting, greeting+strlen(greeting)});
} while (!ec);
}
You'll see that any client, connection e.g. with netcat localhost 6767 will receive the greeting, after which, surprisingly the connection will stay open, instead of being closed.
You'd expect the connection to be closed by the server side either way, either because
a transmission error occurred in async_send
or because after the completion handler is run, it is destroyed and hence the captured shared-pointers are destructed. Not only would that free the copied buffer, but also would it run the destructor of socket which would close the connection.
This clearly confirms that the completion handler never runs. The fix is "easy", find a place to run the service:
int main() {
asio::io_service svc;
tcp::acceptor a(svc, tcp::v4());
a.set_option(tcp::acceptor::reuse_address());
a.bind({{}, 6767});
a.listen();
std::thread th;
{
asio::io_service::work keep(svc); // prevent service running out of work early
th = std::thread([&svc] { svc.run(); });
boost::system::error_code ec;
for (int i = 0; i < 11 && !ec; ++i) {
TcpConnection conn(svc);
a.accept(conn.socket(), ec);
char const* greeting = "whale hello there!\n";
conn.Send({greeting, greeting+strlen(greeting)});
}
}
th.join();
}
This runs 11 connections and exits leak-free.
Better:
It becomes a lot cleaner when the accept loop is also async, and the TcpConnection is properly shared as hinted above:
Live On Coliru
#include <boost/asio.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
#include <memory>
#include <thread>
#include <iostream>
auto& logwarning = std::clog;
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
using Buffer = std::vector<uint8_t>;
TcpConnection(asio::io_service& svc) : m_socket(svc) {}
void start() {
char const* greeting = "whale hello there!\n";
Send({greeting, greeting+strlen(greeting)});
}
void Send(Buffer);
private:
friend struct Server;
Buffer m_output;
tcp::socket m_socket;
};
struct Server {
Server(unsigned short port) {
_acceptor.set_option(tcp::acceptor::reuse_address());
_acceptor.bind({{}, port});
_acceptor.listen();
do_accept();
}
~Server() {
keep.reset();
_svc.post([this] { _acceptor.cancel(); });
if (th.joinable())
th.join();
}
private:
void do_accept() {
auto conn = std::make_shared<TcpConnection>(_svc);
_acceptor.async_accept(conn->m_socket, [this,conn](boost::system::error_code ec) {
if (ec)
logwarning << "accept failed: " << ec.message() << "\n";
else {
conn->start();
do_accept();
}
});
}
asio::io_service _svc;
// prevent service running out of work early:
std::unique_ptr<asio::io_service::work> keep{std::make_unique<asio::io_service::work>(_svc)};
std::thread th{[this]{_svc.run();}}; // TODO handle handler exceptions
tcp::acceptor _acceptor{_svc, tcp::v4()};
};
void TcpConnection::Send(Buffer buffer) {
m_output = std::move(buffer);
auto self = shared_from_this();
m_socket.async_send(asio::buffer(m_output),
[self](const boost::system::error_code &err, size_t /*bytesSent*/) {
if (err) {
logwarning << "clientcomms_t::sendNext encountered error: " << err.message() << "\n";
// not holding on to `self` means the socket gets closed
}
// do more with `self` which points to the TcpConnection instance...
});
}
int main() {
Server server(6868);
std::this_thread::sleep_for(std::chrono::seconds(3));
}

async_connect doesn't call handler in TCP client class

I'm trying to make a client class from boost TCP client example for my projects, and I've noticed that sometimes handle_connect doesn't get called when connecting to nonexistent host.
I've read similar issues here on stack, where people forgot to run io_service or called it before any tasks were posted, but I don't think that's my case, since I launch io_service.run() thread right after calling async_connect, and successfull connect, network unreachable, and some other cases I've tested work just fine.
Here is the full listing:
tcp_client.hpp
#ifndef TCP_CLIENT_HPP
#define TCP_CLIENT_HPP
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <mutex>
#include <iostream>
#include <iomanip>
namespace com {
using boost::asio::ip::tcp;
using namespace std;
class client : public boost::enable_shared_from_this<client> {
private:
std::mutex mx_;
bool stopped_ = 1;
boost::asio::streambuf ibuf_;
boost::shared_ptr<boost::asio::io_service> io_service_;
boost::shared_ptr<boost::asio::ip::tcp::socket> sock_;
boost::shared_ptr<tcp::resolver::iterator> ei_;
std::vector<std::string> inbound_;
std::string host_, port_;
public:
client() {}
void connect( std::string host, std::string port ) {
if (!stopped_) stop();
host_ = host; port_ = port;
io_service_.reset(new boost::asio::io_service);
sock_.reset(new boost::asio::ip::tcp::socket(*io_service_));
ei_.reset(new tcp::resolver::iterator);
tcp::resolver r(*io_service_);
ei_ = boost::make_shared<tcp::resolver::iterator>( r.resolve(tcp::resolver::query(host_, port_)) );
stopped_ = 0;
start_connect();
boost::thread work( boost::bind(&client::work, shared_from_this()) );
return;
}
bool is_running() {
return !stopped_;
}
void stop() {
stopped_ = 1;
sock_->close();
return;
}
void send(std::string str) {
if (stopped_) return;
auto msg = boost::asio::buffer(str, str.size());
boost::asio::async_write( (*sock_), msg, boost::bind(&client::handle_write, shared_from_this(), _1) );
return;
}
std::string pull() {
std::lock_guard<std::mutex> lock(mx_);
std::string msg;
if (inbound_.size()>0) {
msg = inbound_.at(0);
inbound_.erase(inbound_.begin());
}
return msg;
}
int size() {
std::lock_guard<std::mutex> lock(mx_);
return inbound_.size();
}
void clear() {
std::lock_guard<std::mutex> lock(mx_);
inbound_.clear();
return;
}
private:
void work() {
if (stopped_) return;
std::cout<<"work in"<<std::endl;
io_service_->run();
std::cout<<"work out"<<std::endl;
return;
}
void start_connect() {
if ((*ei_) != tcp::resolver::iterator()) {
std::cout<<"Trying "<<(*ei_)->endpoint()<<std::endl;
sock_->async_connect( (*ei_)->endpoint(), boost::bind(&client::handle_connect, shared_from_this(), boost::asio::placeholders::error) );
} else {
stop();
}
return;
}
void handle_connect(const boost::system::error_code& ec) {
if (stopped_) return;
if (!sock_->is_open()) {
std::cout<<"Socket closed"<<std::endl;
(*ei_)++;
start_connect();
} else if (ec) {
std::cout<<"Connect error: "<<ec.message()<<std::endl;
sock_->close();
(*ei_)++;
start_connect();
} else {
std::cout<<"Connected to "<<(*ei_)->endpoint()<<std::endl;
start_read();
}
return;
}
void start_read() {
if (stopped_) return;
boost::asio::async_read_until((*sock_), ibuf_, "", boost::bind(&client::handle_read, shared_from_this(), boost::asio::placeholders::error));
return;
}
void handle_read(const boost::system::error_code& ec) {
std::lock_guard<std::mutex> lock(mx_);
if (stopped_) return;
if (ec) {
std::cout<<"Read error: "<<ec.message()<<std::endl;
stop();
return;
}
std::string line;
std::istream is(&ibuf_);
std::getline(is, line);
if (!line.empty() && inbound_.size()<1000) inbound_.push_back(line);
start_read();
return;
}
private:
void handle_write(const boost::system::error_code& ec) {
if (stopped_) return;
if (ec) {
std::cout<<"Write error: "<<ec.message()<<std::endl;
stop();
return;
}
return;
}
};
};
and tcp_test.cpp
#include "tcp_client.hpp"
int main(int argc, char* argv[]) {
auto tcp_client = boost::shared_ptr<com::client>(new com::client);
try {
tcp_client->connect("192.168.1.15", "50000");
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
tcp_client->connect("192.168.1.20", "50000");
} catch (std::exception& e) {
std::cerr<<"Exception: "<<e.what()<<std::endl;
}
int cnt=0;
while (cnt<5) {
std::cout<<cnt<<std::endl;
cnt++;
tcp_client->send("<test>");
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
}
tcp_client->stop();
while (tcp_client->size()>0) std::cout<<tcp_client->pull()<<std::endl;
return 0;
}
The output I get is when connecting to loopback server:
Trying 192.168.1.15:50000
work in
work out
Trying 192.168.1.20:50000
0
work in
Connected to 192.168.1.20:50000
1
2
3
4
work out
<test>
<test>
<test>
<test>
<test>
The 192.168.1.20 works just as it should, as you see. The 192.168.1.15 doesnt'e exist, but I've expected it to throw some kind of error. Instead io_service.run() returns right away, like async_connect never posted callback task. Maybe it's related to endpoint iterator and not async_connect?
Can anyone please explain why is it happening like this?
Then I've tried to isolate the problem in this code:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>
boost::asio::io_service io_svc;
boost::asio::ip::tcp::socket sock(io_svc);
boost::asio::ip::tcp::resolver::iterator ei;
void work() {
std::cout<<"work in"<<std::endl;
io_svc.run();
std::cout<<"work out"<<std::endl;
return;
}
void stop() {
sock.close();
return;
}
void start_connect();
void handle_connect(const boost::system::error_code& ec) {
if (!sock.is_open()) {
std::cout<<"Socket closed"<<std::endl;
ei++;
start_connect();
} else if (ec) {
std::cout<<"Connect error: "<<ec.message()<<std::endl;
sock.close();
ei++;
start_connect();
} else {
std::cout<<"Connected to "<<ei->endpoint()<<std::endl;
}
return;
}
void start_connect() {
if (ei != boost::asio::ip::tcp::resolver::iterator()) {
std::cout<<"Trying "<<ei->endpoint()<<std::endl;
sock.async_connect( ei->endpoint(), boost::bind(handle_connect, boost::asio::placeholders::error) );
} else {
stop();
}
return;
}
int main(int argc, char* argv[]) {
std::string host="192.168.1.15", port="50000";
boost::asio::ip::tcp::resolver r(io_svc);
ei = r.resolve(boost::asio::ip::tcp::resolver::query(host, port));
start_connect();
boost::thread* thr = new boost::thread(work);
boost::this_thread::sleep_for(boost::chrono::milliseconds(2000));
return 0;
}
But I've got a totally different result. When I try to connect to a nonexistent host, most of the time it's:
Trying 192.168.1.15:50000
work in
Sometimes it's:
Trying 192.168.1.15:50000
work in
Connect error: Operation canceled
Connect error: Operation canceled
And rarely it's:
Trying 192.168.1.15:50000
work in
Segmentation fault
"work out" is never printed, so I'm guessing io_service in this example is doing something, but how is this different from previous code, and why I get "operation canceled" error only sometimes?
A client running in a background thread should look something like this.
Note that I have note included things like connection timeouts. For that you'd want to have a deadline timer running in parallel with the async_connect. Then you'd have to correctly handle crossing cases (hint: cancel the deadline timer on successful connect and throw away the ensuing error from its async_wait).
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <thread>
#include <functional>
boost::asio::io_service io_svc;
struct client
: std::enable_shared_from_this<client>
{
using protocol = boost::asio::ip::tcp;
using resolver = protocol::resolver;
using socket = protocol::socket;
using error_code = boost::system::error_code;
client(boost::asio::io_service& ios)
: ios_(ios) {}
void start(std::string const& host, std::string const& service)
{
auto presolver = std::make_shared<resolver>(get_io_service());
presolver->async_resolve(protocol::resolver::query(host, service),
strand_.wrap([self = shared_from_this(), presolver](auto&& ec, auto iter)
{
self->handle_resolve(ec, presolver, iter);
}));
}
private:
void
handle_resolve(boost::system::error_code const& ec, std::shared_ptr<resolver> presolver, resolver::iterator iter)
{
if (ec) {
std::cerr << "error resolving: " << ec.message() << std::endl;
}
else {
boost::asio::async_connect(sock, iter, strand_.wrap([self = shared_from_this(),
presolver]
(auto&& ec, auto iter)
{
self->handle_connect(ec, iter);
// note - we're dropping presolver here - we don't need it any more
}));
}
}
void handle_connect(error_code const& ec, resolver::iterator iter)
{
if (ec) {
std::cerr << "failed to connect: " << ec.message() << std::endl;
}
else {
auto payload = std::make_shared<std::string>("Hello");
boost::asio::async_write(sock, boost::asio::buffer(*payload),
strand_.wrap([self = shared_from_this(),
payload] // note! capture the payload so it continues to exist during async send
(auto&& ec, auto size)
{
self->handle_send(ec, size);
}));
}
}
void handle_send(error_code const& ec, std::size_t size)
{
if (ec) {
std::cerr << "send failed after " << size << " butes : " << ec.message() << std::endl;
}
else {
// send something else?
}
}
boost::asio::io_service& get_io_service()
{
return ios_;
}
private:
boost::asio::io_service& ios_;
boost::asio::strand strand_{get_io_service()};
socket sock{get_io_service()};
};
void work()
{
std::cout << "work in" << std::endl;
io_svc.run();
std::cout << "work out" << std::endl;
return;
}
int main(int argc, char *argv[])
{
auto pclient = std::make_shared<client>(io_svc);
std::string host = "192.168.1.15", port = "50000";
pclient->start(host, port);
auto run_thread = std::thread(work);
if (run_thread.joinable())
run_thread.join();
return 0;
}
example output:
work in
<time passes>...
failed to connect: Operation timed out
work out

boost::asio::io_service destructor runs very long time

I'm novice in boost::asio and have the first own troubles.
I create a simple Host resolver (see full code below).
Problem 1.
In case of lost Internet connection, my host resolver stops resolving after the first enter into deadline_timer.
My assumption, that "localhost" must be resolved at any time. But "localhost" are not resolved after timeout during resolving google.us (for example, we unplugged Ethernet jack).
The same behaviour in case of resolving unexisted TLD (for example, google.usd instead google.us).
Problem 2.
In case of lost Internet connection, destructor io_service runs very long (usually, 5 seconds).
What's wrong?
I use VS2012, boost 1.54
File hostresolver.h
pragma once
#include <set>
#include <boost/system/error_code.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ip/basic_resolver.hpp>
#include <boost/asio/ip/basic_resolver_iterator.hpp>
typedef std::set<unsigned long> hostresolver_result_container;
class hostresolver
{
public:
hostresolver(boost::asio::io_service* io_service);
~hostresolver(void);
boost::asio::io_service* ios_ptr;
boost::asio::ip::tcp::resolver resolver_;
boost::asio::deadline_timer timer_;
volatile bool is_completed;
bool is_timeout;
std::string hostname;
hostresolver_result_container result;
void on_timeout(const boost::system::error_code &err);
void start_resolve(const char* hostname, int timeout_seconds);
void finish_resolve(const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator endpoint_iterator);
private:
void stop();
};
File hostresolver.cpp
#include "stdafx.h"
#include "hostresolver.h"
#include <boost/bind.hpp>
hostresolver::hostresolver(boost::asio::io_service* io_service) :
resolver_(*io_service), timer_(*io_service), is_completed(false), is_timeout(false)
{
ios_ptr = io_service;
}
hostresolver::~hostresolver(void)
{
}
void hostresolver::start_resolve(const char* hostname, int timeout_second)
{
this->hostname.assign(hostname);
timer_.expires_from_now(boost::posix_time::seconds(timeout_second));
timer_.async_wait(boost::bind(&hostresolver::on_timeout, this, _1));
boost::asio::ip::tcp::resolver::query query(hostname, "http");
resolver_.async_resolve(query,
boost::bind(&hostresolver::finish_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
do
{
ios_ptr->run_one();
}
while (!is_completed);
}
void hostresolver::stop()
{
resolver_.cancel();
timer_.cancel();
is_completed = true;
}
void hostresolver::on_timeout(const boost::system::error_code &err)
{
if ((!err) && (err != boost::asio::error::operation_aborted))
{
is_timeout = true;
stop();
}
}
void hostresolver::finish_resolve(const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
if (!err)
{
while (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
{
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
if (endpoint.address().is_v4())
{
result.insert(endpoint.address().to_v4().to_ulong());
}
endpoint_iterator++;
}
}
stop();
}
File main.cpp
#include "stdafx.h"
#include "hostresolver.h"
int _tmain(int argc, _TCHAR* argv[])
{
boost::asio::io_service ios;
for (int i = 0; i < 2; i++)
{
std::cout << "iteration: " << i << std::endl;
{
hostresolver hres(&ios);
hres.start_resolve("localhost", 1);
if (hres.result.size() == 0)
std::cout << "failed" << std::endl;
}
{
hostresolver hres(&ios);
hres.start_resolve("google.usd", 1);
}
}
return 0;
}
After returning from run_once, the io_service would most likely enteres the "stopped" state. Thus, you should call ios_ptr->reset() prior to calling run_once() again. Quoting from run_once reference:
Return Value: The number of handlers that were executed. A zero return
value implies that the io_service object is stopped (the stopped()
function returns true). Subsequent calls to run(), run_one(), poll()
or poll_one() will return immediately unless there is a prior call to
reset().

boost asio async_write : how to not interleaving async_write calls?

Here's my implementation :
Client A send a message for Client B
Server process the message by async_read the right amount of data and
will wait for new data from Client A (in Order not to block Client A)
Afterwards Server will process the information (probably do a mysql
query) and then send the message to Client B with async_write.
The problem is, if Client A send message really fast, async_writes will interleave before the previous async_write handler is called.
Is there a simple way to avoid this problem ?
EDIT 1 :
If a Client C sends a message to Client B just after Client A, the same issue should appear...
EDIT 2 :
This would work ? because it seems to block, I don't know where...
namespace structure {
class User {
public:
User(boost::asio::io_service& io_service, boost::asio::ssl::context& context) :
m_socket(io_service, context), m_strand(io_service), is_writing(false) {}
ssl_socket& getSocket() {
return m_socket;
}
boost::asio::strand getStrand() {
return m_strand;
}
void push(std::string str) {
m_strand.post(boost::bind(&structure::User::strand_push, this, str));
}
void strand_push(std::string str) {
std::cout << "pushing: " << boost::this_thread::get_id() << std::endl;
m_queue.push(str);
if (!is_writing) {
write();
std::cout << "going to write" << std::endl;
}
std::cout << "Already writing" << std::endl;
}
void write() {
std::cout << "writing" << std::endl;
is_writing = true;
std::string str = m_queue.front();
boost::asio::async_write(m_socket,
boost::asio::buffer(str.c_str(), str.size()),
boost::bind(&structure::User::sent, this)
);
}
void sent() {
std::cout << "sent" << std::endl;
m_queue.pop();
if (!m_queue.empty()) {
write();
return;
}
else
is_writing = false;
std::cout << "done sent" << std::endl;
}
private:
ssl_socket m_socket;
boost::asio::strand m_strand;
std::queue<std::string> m_queue;
bool is_writing;
};
}
#endif
Is there a simple way to avoid this problem ?
Yes, maintain an outgoing queue for each client. Inspect the queue size in the async_write completion handler, if non-zero, start another async_write operation. Here is a sample
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <deque>
#include <iostream>
#include <string>
class Connection
{
public:
Connection(
boost::asio::io_service& io_service
) :
_io_service( io_service ),
_strand( _io_service ),
_socket( _io_service ),
_outbox()
{
}
void write(
const std::string& message
)
{
_strand.post(
boost::bind(
&Connection::writeImpl,
this,
message
)
);
}
private:
void writeImpl(
const std::string& message
)
{
_outbox.push_back( message );
if ( _outbox.size() > 1 ) {
// outstanding async_write
return;
}
this->write();
}
void write()
{
const std::string& message = _outbox[0];
boost::asio::async_write(
_socket,
boost::asio::buffer( message.c_str(), message.size() ),
_strand.wrap(
boost::bind(
&Connection::writeHandler,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
)
);
}
void writeHandler(
const boost::system::error_code& error,
const size_t bytesTransferred
)
{
_outbox.pop_front();
if ( error ) {
std::cerr << "could not write: " << boost::system::system_error(error).what() << std::endl;
return;
}
if ( !_outbox.empty() ) {
// more messages to send
this->write();
}
}
private:
typedef std::deque<std::string> Outbox;
private:
boost::asio::io_service& _io_service;
boost::asio::io_service::strand _strand;
boost::asio::ip::tcp::socket _socket;
Outbox _outbox;
};
int
main()
{
boost::asio::io_service io_service;
Connection foo( io_service );
}
some key points
the boost::asio::io_service::strand protects access to Connection::_outbox
a handler is dispatched from Connection::write() since it is public
it wasn't obvious to me if you were using similar practices in the example in your question since all methods are public.
Just trying to improve Sam's great answer. The improvement points are:
async_write tries hard to send every single byte from the buffer(s) before completing, which means you should supply all the input data that you have to the write operation, otherwise the framing overhead may increase due to TCP packets being smaller than they could have been.
asio::streambuf, while being very convenient to use, is not zero-copy. The example below demonstrates a zero-copy approach: keep the input data chunks where they are and use a scatter/gather overload of async_write that takes in a sequence of input buffers (which are just pointers to the actual input data).
Full source code:
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_set>
#include <vector>
using namespace std::chrono_literals;
using boost::asio::ip::tcp;
class Server
{
class Connection : public std::enable_shared_from_this<Connection>
{
friend class Server;
void ProcessCommand(const std::string& cmd) {
if (cmd == "stop") {
server_.Stop();
return;
}
if (cmd == "") {
Close();
return;
}
std::thread t([this, self = shared_from_this(), cmd] {
for (int i = 0; i < 30; ++i) {
Write("Hello, " + cmd + " " + std::to_string(i) + "\r\n");
}
server_.io_service_.post([this, self] {
DoReadCmd();
});
});
t.detach();
}
void DoReadCmd() {
read_timer_.expires_from_now(server_.read_timeout_);
read_timer_.async_wait([this](boost::system::error_code ec) {
if (!ec) {
std::cout << "Read timeout\n";
Shutdown();
}
});
boost::asio::async_read_until(socket_, buf_in_, '\n', [this, self = shared_from_this()](boost::system::error_code ec, std::size_t bytes_read) {
read_timer_.cancel();
if (!ec) {
const char* p = boost::asio::buffer_cast<const char*>(buf_in_.data());
std::string cmd(p, bytes_read - (bytes_read > 1 && p[bytes_read - 2] == '\r' ? 2 : 1));
buf_in_.consume(bytes_read);
ProcessCommand(cmd);
}
else {
Close();
}
});
}
void DoWrite() {
active_buffer_ ^= 1; // switch buffers
for (const auto& data : buffers_[active_buffer_]) {
buffer_seq_.push_back(boost::asio::buffer(data));
}
write_timer_.expires_from_now(server_.write_timeout_);
write_timer_.async_wait([this](boost::system::error_code ec) {
if (!ec) {
std::cout << "Write timeout\n";
Shutdown();
}
});
boost::asio::async_write(socket_, buffer_seq_, [this, self = shared_from_this()](const boost::system::error_code& ec, size_t bytes_transferred) {
write_timer_.cancel();
std::lock_guard<std::mutex> lock(buffers_mtx_);
buffers_[active_buffer_].clear();
buffer_seq_.clear();
if (!ec) {
std::cout << "Wrote " << bytes_transferred << " bytes\n";
if (!buffers_[active_buffer_ ^ 1].empty()) // have more work
DoWrite();
}
else {
Close();
}
});
}
bool Writing() const { return !buffer_seq_.empty(); }
Server& server_;
boost::asio::streambuf buf_in_;
std::mutex buffers_mtx_;
std::vector<std::string> buffers_[2]; // a double buffer
std::vector<boost::asio::const_buffer> buffer_seq_;
int active_buffer_ = 0;
bool closing_ = false;
bool closed_ = false;
boost::asio::deadline_timer read_timer_, write_timer_;
tcp::socket socket_;
public:
Connection(Server& server) : server_(server), read_timer_(server.io_service_), write_timer_(server.io_service_), socket_(server.io_service_) {
}
void Start() {
socket_.set_option(tcp::no_delay(true));
DoReadCmd();
}
void Close() {
closing_ = true;
if (!Writing())
Shutdown();
}
void Shutdown() {
if (!closed_) {
closing_ = closed_ = true;
boost::system::error_code ec;
socket_.shutdown(tcp::socket::shutdown_both, ec);
socket_.close();
server_.active_connections_.erase(shared_from_this());
}
}
void Write(std::string&& data) {
std::lock_guard<std::mutex> lock(buffers_mtx_);
buffers_[active_buffer_ ^ 1].push_back(std::move(data)); // move input data to the inactive buffer
if (!Writing())
DoWrite();
}
};
void DoAccept() {
if (acceptor_.is_open()) {
auto session = std::make_shared<Connection>(*this);
acceptor_.async_accept(session->socket_, [this, session](boost::system::error_code ec) {
if (!ec) {
active_connections_.insert(session);
session->Start();
}
DoAccept();
});
}
}
boost::asio::io_service io_service_;
tcp::acceptor acceptor_;
std::unordered_set<std::shared_ptr<Connection>> active_connections_;
const boost::posix_time::time_duration read_timeout_ = boost::posix_time::seconds(30);
const boost::posix_time::time_duration write_timeout_ = boost::posix_time::seconds(30);
public:
Server(int port) : acceptor_(io_service_, tcp::endpoint(tcp::v6(), port), false) { }
void Run() {
std::cout << "Listening on " << acceptor_.local_endpoint() << "\n";
DoAccept();
io_service_.run();
}
void Stop() {
acceptor_.close();
{
std::vector<std::shared_ptr<Connection>> sessionsToClose;
copy(active_connections_.begin(), active_connections_.end(), back_inserter(sessionsToClose));
for (auto& s : sessionsToClose)
s->Shutdown();
}
active_connections_.clear();
io_service_.stop();
}
};
int main() {
try {
Server srv(8888);
srv.Run();
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
}
}