boost::asio multithreaded asynchronous accept with blocking read/write server - c++

My idea was to create X threads, run it using KeepRunning method which has endless loop calling _io_service.run() and send tasks to _io_service when received a new connection using _io_service.poll() in async_accept handler.
I run the server with a code like this:
oh::msg::OHServer s("0.0.0.0", "9999", 200);
ConsoleStopServer = boost::bind(&oh::msg::OHServer::Stop, &s);
SetConsoleCtrlHandler(bConsoleHandler, TRUE);
s.Run();
but when I receive one connection, then serve it in Post() method using blocking read/writes in MsgWorker class, then all the threads are being closed.
I have code like below (it's some mix from http server3 asio example and mine):
OHServer::OHServer(const std::string& sAddress, const std::string& sPort, std::size_t tps)
: _nThreadPoolSize(tps), _acceptor(_io_service), _sockClient(new boost::asio::ip::tcp::socket(_io_service))
{
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(_io_service);
boost::asio::ip::tcp::resolver::query query(sAddress, sPort);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
_acceptor.open(endpoint.protocol());
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.bind(endpoint);
_acceptor.listen();
_acceptor.async_accept(
*_sockClient,
boost::bind(
&OHServer::AcceptConnection,
this,
boost::asio::placeholders::error
)
);
}
void OHServer::KeepRunning()
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Thread Start" << std::endl;
global_stream_lock.unlock();
while( true )
{
try
{
boost::system::error_code ec;
_io_service.run( ec );
if( ec )
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Error: " << ec << std::endl;
global_stream_lock.unlock();
}
break;
}
catch( std::exception & ex )
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Exception: " << ex.what() << std::endl;
global_stream_lock.unlock();
}
}
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Thread Finish" << std::endl;
global_stream_lock.unlock();
}
void OHServer::Run()
{
// Create a pool of threads to run all of the io_services.
for (std::size_t i = 0; i < _nThreadPoolSize; ++i)
{
boost::shared_ptr<boost::thread> thread(new boost::thread(
boost::bind(&OHServer::KeepRunning, this)));
threads.push_back(thread);
}
cout << "Hit enter to close server" << endl;
cin.get();
}
void OHServer::Stop()
{
boost::system::error_code ec;
_acceptor.close(ec);
_sockClient->shutdown( boost::asio::ip::tcp::socket::shutdown_both, ec );
_sockClient->close( ec );
_io_service.stop();
// Wait for all threads in the pool to exit.
for (std::size_t i = 0; i < threads.size(); ++i)
{
threads[i]->join();
cout << "threads[ "<< i << "]->join();" << endl;
}
}
void OHServer::Post()
{
std::cout << "Accepted new connection." << std::endl;
CMsgWorker *msgWorker = new CMsgWorker(_sockClient);
msgWorker->Start();
delete msgWorker;
}
void OHServer::AcceptConnection(const boost::system::error_code& e)
{
if (!e)
{
_io_service.post(boost::bind(&OHServer::Post, this));
_acceptor.async_accept(
*_sockClient,
boost::bind(
&OHServer::AcceptConnection,
this,
boost::asio::placeholders::error
)
);
}
}
What should I do for the threads to be still waiting for some work to do from _io_service?
Thanks for any help!

Check it out:
// Kick off 5 threads
for (size_t i = 0; i < 5; ++i) {
boost::thread* t = threads.create_thread(boost::bind(&boost::asio::io_service::run, &io));
std::cout << "Creating thread " << i << " with id " << t->get_id() << std::endl;
}
See the timer.cc example here for an idea on how to do this: https://github.com/sean-/Boost.Examples/tree/master/asio/timer

Finally I've ended up with some easy-to-use version of server:
Usage:
boost::shared_ptr<CTCPServer> _serverPtr;
void CMyServer::Start()
{
//First we must create a few threads
thread* t = 0;
for (int i = 0; i < COHConfig::_iThreads; ++i)
{
t =_threads.create_thread(bind(&io_service::run, &_io_service));
}
//Then we create a server object
_serverPtr.reset( new CTCPServer(&_io_service, PORT_NUMBER) );
//And finally run the server through io_service
_io_service.post(boost::bind(&CMyServer::RunServer, _serverPtr, &CMyServer::HandleMessage));
}
//This is the function which is called by io_service to start our server
void CMyServer::RunServer(CTCPServer* s, void (*HandleFunction)(shared_ptr<ip::tcp::socket>, deadline_timer*))
{
s->Run(HandleFunction);
}
//And this is our connection handler
void CMyServer::HandleMessage(shared_ptr< ip::tcp::socket > sockClient, deadline_timer* timer)
{
cout << "Handling connection from: " << sockClient->remote_endpoint().address().to_string() << ":" << sockClient->remote_endpoint().port() << endl;
//This is some class which gets socket in its constructor and handles the connection
scoped_ptr<CMyWorker> myWorker( new CMyWorker(sockClient) );
msgWorker->Start();
}
//Thanks to this function we can stop our server
void CMyServer::Stop()
{
_serverPtr->Stop();
}
The TCPServer.hpp file:
#ifndef TCPSERVER_HPP
#define TCPSERVER_HPP
#if defined(_WIN32)
#define BOOST_THREAD_USE_LIB
#endif
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <string>
#include <vector>
class CTCPServer: private boost::noncopyable
{
private:
bool bKeepRunning;
boost::asio::io_service* _io_service;
std::string _sPort;
boost::asio::ip::tcp::acceptor _acceptor;
boost::shared_ptr< boost::asio::ip::tcp::socket > _sockClient;
boost::asio::deadline_timer _timer;
bool _bIPv6;
std::string SessionID();
public:
CTCPServer(boost::asio::io_service* ios, const std::string& sPort, bool bIPv6=false):
_sPort(sPort),
_acceptor(*ios),
_timer(*ios),
_bIPv6(bIPv6)
{
_io_service = ios;
bKeepRunning = false;
};
void Run(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > sock, boost::asio::deadline_timer* timer));
void AsyncAccept(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ));
void AcceptHandler(const boost::system::error_code& e, void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket >, boost::asio::deadline_timer* ));
void Stop();
void Stop(void (*StopFunction)());
};
#endif
The TCPServer.cpp file:
#include "TCPServer.hpp"
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
using namespace std;
string CTCPServer::SessionID()
{
ostringstream outs;
outs << "[" << boost::this_thread::get_id() << "] ";
return outs.str();
}
void CTCPServer::Run(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ))
{
try
{
boost::asio::ip::tcp::resolver resolver(*_io_service);
boost::asio::ip::tcp::endpoint endpoint;
if(_bIPv6)
{
boost::asio::ip::tcp::resolver::query queryv6(boost::asio::ip::tcp::v6(), _sPort);
endpoint = *resolver.resolve(queryv6);
}
else
{
boost::asio::ip::tcp::resolver::query queryv4(boost::asio::ip::tcp::v4(), _sPort);
endpoint = *resolver.resolve(queryv4);
}
_acceptor.open(endpoint.protocol());
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.set_option(boost::asio::socket_base::enable_connection_aborted(true));
_acceptor.bind(endpoint);
_acceptor.listen();
boost::system::error_code ec;
bKeepRunning = true;
AsyncAccept(HandleFunction);
}
catch(std::exception& e)
{
if(!_bIPv6)
std::cerr << "Exception wile creating IPv4 TCP socket on port "<< _sPort<< ": " << e.what() << std::endl;
else
std::cerr << "Exception wile creating IPv6 TCP socket on port "<< _sPort<< ": " << e.what() << std::endl;
}
}
void CTCPServer::AsyncAccept(void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket > , boost::asio::deadline_timer* ))
{
if(bKeepRunning)
{
try
{
_sockClient.reset(new boost::asio::ip::tcp::socket(*_io_service));
cout << SessionID() << "Waiting for connection on port: " << _sPort << endl;
_acceptor.async_accept(*_sockClient, boost::bind(&CTCPServer::AcceptHandler, this, boost::asio::placeholders::error, HandleFunction));
}
catch(exception& e)
{
string sWhat = e.what();
cout << SessionID() << "Error while accepting connection: " << e.what() << endl;
}
}
}
void CTCPServer::AcceptHandler(const boost::system::error_code& e,
void (*HandleFunction)(boost::shared_ptr< boost::asio::ip::tcp::socket >,
boost::asio::deadline_timer* ))
{
if(!e)
{
try
{
(*_io_service).post(boost::bind(HandleFunction, _sockClient, &_timer));
AsyncAccept(HandleFunction);
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
}
}
void CTCPServer::Stop()
{
cout << SessionID() << "STOP port " << _sPort << endl;
if(!bKeepRunning)
return;
bKeepRunning = false;
try
{
_sockClient->close();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
try
{
_acceptor.cancel();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
try
{
_acceptor.close();
}
catch(exception& e)
{
cout << SessionID() << "Exception: " << e.what() << endl;
}
}
void CTCPServer::Stop(void (*StopFunction)())
{
Stop();
StopFunction();
}
It's also very easy to modify to be IPv6 compatible.
It's already tested and working very well. Just copy it and use!

Related

Unexpected invocation of the destructor after an invokation of a product of std::bind

There is a simple example with making use of boost::asio::io_context
https://github.com/unegare/boost-ex/blob/500e46f4d3b41e2abe48e2deccfab39d44ae94e0/main.cpp
#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <thread>
#include <vector>
#include <memory>
#include <mutex>
#include <chrono>
#include <iostream>
#include <exception>
std::mutex m_stdout;
class MyWorker {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
public:
MyWorker(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard):
io_context(_io_context), work_guard(_work_guard) {}
MyWorker(const MyWorker &mw): io_context(mw.io_context), work_guard(mw.work_guard) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker copy constructor" << std::endl;
m_stdout.unlock();
}
MyWorker(MyWorker &&mw): io_context(std::move(mw.io_context)), work_guard(std::move(mw.work_guard)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker move constructor" << std::endl;
m_stdout.unlock();
}
~MyWorker() {}
void operator() () {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Start" << std::endl;
m_stdout.unlock();
while(true) {
try {
boost::system::error_code ec;
io_context->run(ec);
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: received an error: " << ec << std::endl;
m_stdout.unlock();
continue;
}
break;
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
}
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Finish" << std::endl;
m_stdout.unlock();
}
};
class Client: public std::enable_shared_from_this<Client> {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
std::shared_ptr<boost::asio::ip::tcp::socket> sock;
std::shared_ptr<std::array<char, 512>> buff;
public:
Client(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard, std::shared_ptr<boost::asio::ip::tcp::socket> &_sock):
io_context(_io_context), work_guard(_work_guard), sock(_sock) {
buff = std::make_shared<std::array<char,512>>();
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " with args" << std::endl;
m_stdout.unlock();
}
Client(const Client &cl): io_context(cl.io_context), work_guard(cl.work_guard), sock(cl.sock), buff(cl.buff) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " copy" << std::endl;
m_stdout.unlock();
}
Client(Client &&cl): io_context(std::move(cl.io_context)), work_guard(std::move(cl.work_guard)), sock(std::move(cl.sock)), buff(std::move(cl.buff)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " move" << std::endl;
m_stdout.unlock();
}
~Client() {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " buff.use_count: " << buff.use_count() << " | sock.use_count: " << sock.use_count() << " | io_context.use_count: " << io_context.use_count() << std::endl;
m_stdout.unlock();
}
void OnConnect(const boost::system::error_code &ec) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
// buff = std::make_shared<std::array<char, 512>>();
char req[] = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
memcpy(buff->data(), req, strlen(req));
m_stdout.lock();
std::cout << req << std::endl;
m_stdout.unlock();
sock->async_write_some(boost::asio::buffer(buff->data(), strlen(buff->data())), std::bind(std::mem_fn(&Client::OnSend), this, std::placeholders::_1, std::placeholders::_2));
}
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
}
void OnSend(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << " use_count: " << io_context.use_count() << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
buff->fill(0);
std::cout << __FUNCTION__ << std::endl;
sock->async_read_some(boost::asio::buffer(buff->data(), buff->size()), std::bind(std::mem_fn(&Client::OnRecv), this, std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
m_stdout.lock();
std::cout << buff->data() << std::endl;
m_stdout.unlock();
}
}
};
int main () {
std::shared_ptr<boost::asio::io_context> io_context(std::make_shared<boost::asio::io_context>());
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard(
std::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> (boost::asio::make_work_guard(*io_context))
);
MyWorker mw(io_context, work_guard);
std::vector<std::thread> vth;
vth.reserve(1);
for (int i = 1; i > 0; --i) {
vth.emplace_back(mw);
}
std::shared_ptr<Client> cl = 0;
try {
boost::asio::ip::tcp::resolver resolver(*io_context);
boost::asio::ip::tcp::resolver::query query("unegare.info", "80");
boost::asio::ip::tcp::endpoint ep = *resolver.resolve(query);
m_stdout.lock();
std::cout << "ep: " << ep << std::endl;
m_stdout.unlock();
std::shared_ptr<boost::asio::ip::tcp::socket> sock(std::make_shared<boost::asio::ip::tcp::socket>(*io_context));
std::shared_ptr<Client> cl2(std::make_shared<Client>(io_context, work_guard, sock));
cl = cl2->shared_from_this();
m_stdout.lock();
std::cout << "HERE: use_count = " << cl.use_count() << std::endl;
m_stdout.unlock();
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
std::this_thread::sleep_for(std::chrono::duration<double>(1));
m_stdout.lock();
std::cout << "AFTER CALL" << std::endl;
m_stdout.unlock();
// asm volatile ("");
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
try {
char t;
std::cin >> t;
work_guard->reset();
// std::this_thread::sleep_for(std::chrono::duration<double>(1));
// std::cout << "Running" << std::endl;
// io_context->run();
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
std::for_each(vth.begin(), vth.end(), std::mem_fn(&std::thread::join));
return 0;
}
stdout:
[140487203505984] MyWorker copy constructor
[140487203505984] MyWorker move constructor
[140487185372928] Thread Start
ep: 95.165.130.37:80
[140487203505984] Client with args
HERE: use_count = 2
[140487203505984] Client copy
[140487203505984] Client move
[140487203505984] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
[140487185372928] Client move
[140487185372928] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
OnConnect
GET / HTTP/1.1
Host: unegare.info
OnConnect use_count: 2
[140487185372928] ~Client buff.use_count: 2 | sock.use_count: 3 | io_context.use_count: 5
Segmentation Fault (core dumped)
But there is a little problem with the understanding of the segfault caused by the bad reference to the Client object.
But I do not understand why cl2 becomes destructed after the call of
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
at the 162 line.
As well as ... Why was there an invocation of the copy constructor?
as it may be noticed from the stdout above.
It's good that you try to understand. However, start simple!
your copy constructor fails to call the base-class copy constructor (!!) oops
your binds bind to
this (which does NOT keep the shared pointer alive)
*cl2->shared_from_this() - oops this binds to a COPY of *cl2 by value¹. That is obviously the reason why that COPY is destructed when you're done
The invalid reference arose from the combination of 1. and 2.b.
I'd suggest to simplify. A lot!
Use Rule Of Zero (only declare special members when you need. In this, you introduced a bug because you did something wrong manually writing a copy-constructor that you didn't need)
Use non-owning references where appropriate (not everything needs to be a smart pointer)
Prefer std::unique_ptr for things that do not require shared ownership (shared ownership should be really rare)
Prefer std::lock_guard instead of manually locking and unlocking (that's not exception safe)
Many others: work_guard doesn't need to be copied, it has a reset() member already, catch by const-reference, if you're gonna catch, don't need to use error_code on io_context::run, check the end-point iterator of resolve before dereference, use boost::asio::connect instead so you don't have to check and iterate over different endpoints etc.
prefer std::string over fixed-size buffers if you're doing dynamic allocation anyways, use non-implementation defined chrono durations (for example 1.0s, not duration<double>(1)), consider using boost::thread_group instead of vector<std::thread> or at least only joining threads that are joinable() etc
Suggest to make that async_connect part of Client, since then you can simply bind members the same way as all the rest of the binds (using shared_from_this(), instead of writing the bug you had)
If you have time left, consider using Beast for the Http request and response creation/parsing
Just as a reference, this is what MyWorker could be:
class MyWorker {
ba::io_context& io_context;
public:
MyWorker(ba::io_context& _io_context) : io_context(_io_context) {}
//// best is to not mention these at all, because even `default`ing can change what compiler generates
//MyWorker(const MyWorker& mw) = default;
//MyWorker(MyWorker&& mw) = default;
//~MyWorker() = default;
void operator()() {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
}
};
At this point, you could very well just make it a lambda:
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
Much simpler.
The Full Code Simplified
Just see e.g. the simplicity of the new connection code:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
The endpoint is printed when OnConnect is called.
Live On Coliru²
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
namespace ba = boost::asio;
using ba::ip::tcp;
using namespace std::literals;
static std::mutex s_stdout;
#define TRACE(expr) { \
std::lock_guard<std::mutex> lk(s_stdout); \
std::cout << "[" << std::this_thread::get_id() << "] " << expr << std::endl; \
}
class Client : public std::enable_shared_from_this<Client> {
ba::io_context& io_context;
tcp::socket sock;
std::string buf;
public:
Client(ba::io_context& _io_context) : io_context(_io_context), sock{io_context}
{ }
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
std::bind(std::mem_fn(&Client::OnConnect), shared_from_this(), std::placeholders::_1));
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
std::bind(std::mem_fn(&Client::OnSend), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '\0');
sock.async_read_some(ba::buffer(buf), std::bind(std::mem_fn(&Client::OnRecv), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code& ec, std::size_t bytes_transferred) {
TRACE(__FUNCTION__);
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
buf.resize(bytes_transferred);
TRACE(std::quoted(buf));
}
}
};
int main() {
ba::io_context io_context;
auto work_guard = make_work_guard(io_context);
boost::thread_group vth;
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
vth.create_thread(mw);
try {
std::make_shared<Client>(io_context)->Start();
char t;
std::cin >> t;
work_guard.reset();
} catch (std::exception const& ex) {
TRACE("Main Thread: caught an exception: " << ex.what());
}
vth.join_all();
}
Prints:
[140095938852608] Thread Start
[140095938852608] OnConnect ep:95.165.130.37:80
[140095938852608] "GET / HTTP/1.1
Host: unegare.info
"
[140095938852608] OnSend
[140095938852608] OnRecv
[140095938852608] "HTTP/1.1 200 OK
Date: Sun, 22 Dec 2019 22:26:56 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Sun, 10 Mar 2019 10:17:38 GMT
ETag: \"37-583bac3f3c843\"
Accept-Ranges: bytes
Content-Length: 55
Content-Type: text/html
<html>
<head>
</head>
<body>
It works.
</body>
</html>
"
q
[140095938852608] Thread Finish
BONUS
std::bind is obsolete since C++11, consider using lambdas instead. Since Coliru didn't want to co-operate any more, I'll just post the three changed functions in full:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnSend(ec, bytes_transferred); });
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '\0');
sock.async_read_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnRecv(ec, bytes_transferred); });
}
}
¹ Does boost::bind() copy parameters by reference or by value?
² Coliru doesn't allow network access
You don’t track destruction of cl2 with you output: cl2 is std::shared_ptr<Client>. You seem to track construction of Client objects.
You problem is the * in front of cl2->make_shared_from_this(): that will dereference the std::shared_ptr<Client>. The bind() expression sees a Client& and captures a Client by copy. Removing the * should fix the problem. I haven’t fully understood was code is trying to do (I’m reading it on a phone) but I guess you actually want to capture the std::shared_ptr<Client> rather than the Client.
Also, as cl2 is already a std::shared_ptr<Client> there is no point in calling make_shared_from_this() on the pointed to object. It just recreates an unnecessary st::shared_ptr<Client>.

Boost ASIO system error 995: The I/O operation has been aborted

I am trying to write a simple server that allows multiple clients to connect. When I run my program, I get the following error as output:
[3730] Press [return] to exit.
Listening on: 127.0.0.1:7777
[5d0] Thread Start
Error: system:995 The I/O operation has been aborted because of either a
thread exit or an application request
I think this is because my Client object is falling out of scope, but I'm not entirely sure if this is actually the problem or if there's something else wrong with my code.
ClientNetwork::ClientNetwork(asio::io_service& ios, const asio::ip::tcp::endpoint& edp) : mIOService(ios), mAcceptor(ios, edp)
{
/*mAcceptor.open(edp.protocol());
mAcceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(false));*/
listen();
}
ClientNetwork::~ClientNetwork()
{
boost::system::error_code ec;
mAcceptor.close(ec);
mConnectionThreads.join_all();
mClients.clear();
std::cout << "KILLING NETWORK" << std::endl;
}
void ClientNetwork::listen()
{
mConnectionThreads.create_thread(boost::bind(&ClientNetwork::connectionThread, this));
Client* c = new Client(mIOService);
mAcceptor.listen(boost::asio::socket_base::max_connections);
mAcceptor.async_accept(*c->getSocket(), boost::bind(&ClientNetwork::handleAccept, this, _1, c));
}
void ClientNetwork::handleAccept(const boost::system::error_code& error, Client* client)
{
if (!error)
{
mClients.push_back(client);
mConnectionThreads.create_thread(boost::bind(&ClientNetwork::connectionThread, this));
std::cout << mConnectionThreads.size() << std::endl;
}
else
{
std::cout << "Error: " << error << " " << error.message() << std::endl;
}
}
This is my main (not sure it's entirely helpful but felt it would be good to include:
int main(int argc, char * argv[])
{
boost::shared_ptr< boost::asio::io_service > io_service(
new boost::asio::io_service
);
boost::shared_ptr< boost::asio::io_service::work > work(
new boost::asio::io_service::work(*io_service)
);
boost::shared_ptr< boost::asio::io_service::strand > strand(
new boost::asio::io_service::strand(*io_service)
);
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Press [return] to exit." << std::endl;
global_stream_lock.unlock();
/*boost::thread_group worker_threads;
for (int x = 0; x < 2; ++x)
{
worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
}*/
/*boost::shared_ptr< boost::asio::ip::tcp::acceptor > acceptor(
new boost::asio::ip::tcp::acceptor(*io_service)
);
boost::shared_ptr< boost::asio::ip::tcp::socket > sock(
new boost::asio::ip::tcp::socket(*io_service)
);*/
try
{
boost::asio::ip::tcp::resolver resolver(*io_service);
boost::asio::ip::tcp::resolver::query query(
"127.0.0.1",
boost::lexical_cast< std::string >(7777)
);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
ClientNetwork clientNet(*io_service, endpoint);
global_stream_lock.lock();
std::cout << "Listening on: " << endpoint << std::endl;
global_stream_lock.unlock();
}
catch (std::exception & ex)
{
global_stream_lock.lock();
std::cout << "[" << boost::this_thread::get_id()
<< "] Exception: " << ex.what() << std::endl;
global_stream_lock.unlock();
}
std::cin.get();
//worker_threads.join_all();
return 0;
}
After creating ClientNetwork object and when first an asynchronous operation was started you should run io_service::run method to process handlers in events loop then your program runs until there is at least one asynchronous operation to be executed.
At the end of try scope put io_service->run():
ClientNetwork clientNet(*io_service, endpoint);
global_stream_lock.lock();
std::cout << "Listening on: " << endpoint << std::endl;
global_stream_lock.unlock();
io_service->run();
Without this the destructor of ClientNetwork class is called and mAcceptor is closed with printing this error message
void ClientNetwork::handleAccept (...) {
...
std::cout << "Error: " << error << " " << error.message() << std::endl;
}

Boost ASIO async_read is not reading data from the client

I have a server/client application which works for a write from client to a read at server.
Inside the startHandlig function in the server code, if I comment async_connect_1 and the return after it, then it works fine which involves sync write function.
I added async_connect_1 function inside Service() class to asynchronously read from the socket.
This function is called when a client connects to the server and this function returns immediately.
I expect the callback function corresponding to async_read to be called, but that is not happening...
I'm stuck at this since a long time.. Appreciate help on this...
Server Code
#include <boost/asio.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/tuple/tuple.hpp>
#include <thread>
#include <atomic>
#include <memory>
#include <iostream>
#include "../stocks.hpp"
using namespace boost;
class Service {
public:
Service(){}
void StartHandligClient(
std::shared_ptr<asio::ip::tcp::socket> sock) {
read_async_1(sock);
return;
std::thread th(([this, sock]() {
HandleClient(sock);
}));
std::cout << "Detached \n";
th.detach();
}
private:
void read_async_1(std::shared_ptr<asio::ip::tcp::socket> sock)
{
if(!(*sock.get()).is_open())
{
std::cout << getpid() << " : Socket closed in sync_read \n" << std::flush;
return ;
}
std::cout << "haha_1\n" << std::flush;
boost::asio::async_read( (*sock.get()), boost::asio::buffer(inbound_header_),
[this](boost::system::error_code ec,
size_t bytesRead)
{
std::cout << "haha_2\n" << std::flush;
if (!ec)
{
int headerBytesReceived = bytesRead;
std::cout << "\n\n headerBytesReceived : " << headerBytesReceived << "\n" << std::flush ;
// this->async_read(sock);
}
else
{
// Terminate connection ?
if(ec == boost::asio::error::eof)
{
std::cout << getpid() << " : ** sync_read : Connection lost : boost::asio::error::eof ** \n";
}
std::cout << "Error occured in sync_read! Error code = " << ec.value() << ". Message: " << ec.message() << "\n" << std::flush;
return ;
}
return ;
}
);
std::cout << getpid() << " : final return from async_read \n" << std::flush;
return ;
}
void HandleClient(std::shared_ptr<asio::ip::tcp::socket> sock) {
while(1)
{
try {
// asio::streambuf request;
// asio::read_until(*sock.get(), request, '\n');
int headerBytesReceived = asio::read( *sock.get(), boost::asio::buffer(inbound_header_) );
std::cout << "headerBytesReceived : " << headerBytesReceived << "\n" << std::flush;
// Determine the length of the serialized data.
std::istringstream is(std::string(inbound_header_, header_length));
std::cout << "is : " << is.str() << ", inbound_header_ : " << inbound_header_ << "\n";
std::size_t inbound_data_size = 0;
if (!(is >> std::hex >> inbound_data_size))
{
// Header doesn't seem to be valid. Inform the caller.
// boost::system::error_code error(boost::asio::error::invalid_argument);
// boost::get<0>(handler)(error);
std::cout << "RET-1 \n";
return;
}
std::cout << "inbound_data_size : " << inbound_data_size << "\n" << std::flush;
// Start an asynchronous call to receive the data.
inbound_data_.resize(inbound_data_size);
std::cout << "inbound_data_.size() : " << inbound_data_.size() << "\n" << std::flush;
int bytesReceived = asio::read( *sock.get(), boost::asio::buffer(inbound_data_) );
std::string archive_data(&inbound_data_[0], inbound_data_.size());
std::istringstream archive_stream(archive_data);
boost::archive::text_iarchive archive(archive_stream);
archive >> stocks_;
std::cout << "bytesReceived : " << bytesReceived << " , stocks_.size() : " << stocks_.size() << "\n";
// Print out the data that was received.
for (std::size_t i = 0; i < stocks_.size(); ++i)
{
std::cout << "Stock number " << i << "\n";
std::cout << " code: " << stocks_[i].code << "\n";
std::cout << " name: " << stocks_[i].name << "\n";
std::cout << " open_price: " << stocks_[i].open_price << "\n";
std::cout << " high_price: " << stocks_[i].high_price << "\n";
std::cout << " low_price: " << stocks_[i].low_price << "\n";
std::cout << " last_price: " << stocks_[i].last_price << "\n";
std::cout << " buy_price: " << stocks_[i].buy_price << "\n";
std::cout << " buy_quantity: " << stocks_[i].buy_quantity << "\n";
std::cout << " sell_price: " << stocks_[i].sell_price << "\n";
std::cout << " sell_quantity: " << stocks_[i].sell_quantity << "\n";
}
}
catch (system::system_error &e)
{
boost::system::error_code ec = e.code();
if(ec == boost::asio::error::eof)
{
std::cout << "EOF Error \n";
}
std::cout << "Server Error occured! Error code = "
<< e.code() << ". Message: "
<< e.what() << "\n";
break;
}
}
// Clean-up.
delete this;
}
/// The size of a fixed length header.
enum { header_length = 8 };
/// Holds an outbound header.
std::string outbound_header_;
/// Holds the outbound data.
std::string outbound_data_;
/// Holds an inbound header.
char inbound_header_[header_length];
/// Holds the inbound data.
std::vector<char> inbound_data_;
std::vector<stock> stocks_;
};
class Acceptor {
public:
Acceptor(asio::io_service& ios, unsigned short port_num) :
m_ios(ios),
m_acceptor(m_ios,
asio::ip::tcp::endpoint(
asio::ip::address_v4::any(),
port_num))
{
m_acceptor.listen();
}
void Accept() {
std::cout << "Server Accept() \n" << std::flush;
std::shared_ptr<asio::ip::tcp::socket>
sock(new asio::ip::tcp::socket(m_ios));
m_acceptor.accept(*sock.get());
(new Service)->StartHandligClient(sock);
}
private:
asio::io_service& m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
class Server {
public:
Server() : m_stop(false) {}
void Start(unsigned short port_num) {
m_thread.reset(new std::thread([this, port_num]() {
Run(port_num);
}));
}
void Stop() {
m_stop.store(true);
m_thread->join();
}
private:
void Run(unsigned short port_num) {
Acceptor acc(m_ios, port_num);
while (!m_stop.load()) {
std::cout << "Server accept\n" << std::flush;
acc.Accept();
}
}
std::unique_ptr<std::thread> m_thread;
std::atomic<bool> m_stop;
asio::io_service m_ios;
};
int main()
{
unsigned short port_num = 3333;
try {
Server srv;
srv.Start(port_num);
std::this_thread::sleep_for(std::chrono::seconds(100));
std::cout << "Stopping server \n";
srv.Stop();
}
catch (system::system_error &e) {
std::cout << "Error occured! Error code = "
<< e.code() << ". Message: "
<< e.what();
}
return 0;
}
Client Code
#include <boost/asio.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind.hpp>
#include <boost/serialization/vector.hpp>
#include <iostream>
#include "../stocks.hpp"
using namespace boost;
class SyncTCPClient {
public:
SyncTCPClient(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_sock.open(m_ep.protocol());
}
void connect() {
m_sock.connect(m_ep);
}
void close() {
m_sock.shutdown(
boost::asio::ip::tcp::socket::shutdown_both);
m_sock.close();
}
std::string emulateLongComputationOp(
unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP "
+ std::to_string(duration_sec)
+ "\n";
sendRequest(request);
return receiveResponse();
};
private:
void sendRequest(const std::string& request) {
std::vector<stock> stocks_;
// Create the data to be sent to each client.
stock s;
s.code = "ABC";
s.name = "A Big Company";
s.open_price = 4.56;
s.high_price = 5.12;
s.low_price = 4.33;
s.last_price = 4.98;
s.buy_price = 4.96;
s.buy_quantity = 1000;
s.sell_price = 4.99;
s.sell_quantity = 2000;
stocks_.push_back(s);
// Serialize the data first so we know how large it is.
std::ostringstream archive_stream;
boost::archive::text_oarchive archive(archive_stream);
archive << stocks_;
outbound_data_ = archive_stream.str();
std::cout << "outbound_data_ : " << outbound_data_ << "\n" << std::flush;
std::cout << "outbound_data_.size() : " << outbound_data_.size() << "\n" << std::flush;
// Format the header.
std::ostringstream header_stream;
header_stream << std::setw(header_length) << std::hex << outbound_data_.size();
std::cout << "header_stream.str() : " << header_stream.str() << "\n" << std::flush;
std::cout << "header_stream.str().size() : " << header_stream.str().size() << "\n" << std::flush;
if (!header_stream || header_stream.str().size() != header_length)
{
// Something went wrong, inform the caller.
// boost::system::error_code error(boost::asio::error::invalid_argument);
// socket_.get_io_service().post(boost::bind(handler, error));
return;
}
outbound_header_ = header_stream.str();
std::cout << "outbound_header_ : " << outbound_header_ << "\n" << std::flush;
// Write the serialized data to the socket. We use "gather-write" to send
// both the header and the data in a single write operation.
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(outbound_header_));
buffers.push_back(boost::asio::buffer(outbound_data_));
std::size_t sizeSent = asio::write(m_sock, buffers);
std::cout << "sizeSent : " << sizeSent << "\n" << std::flush;
}
std::string receiveResponse() {
std::string response;
/*
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::istream input(&buf);
std::getline(input, response);
*/
return response;
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
enum { header_length = 8 };
std::string outbound_data_;
std::string outbound_header_;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client(raw_ip_address, port_num);
// Sync connect.
client.connect();
sleep(1);
std::cout << "Sending request to the server... "
<< std::endl;
std::string response = client.emulateLongComputationOp(10);
std::cout << "Response received: " << response << std::endl;
sleep(100);
std::cout << "\n\n Closing client connection \n\n";
// Close the connection and free resources.
client.close();
}
catch (system::system_error &e) {
std::cout << "Client Error occured! Error code = " << e.code()
<< ". Message: " << e.what();
return e.code().value();
}
return 0;
}
Included File (stocks.hpp)
#ifndef _STOCKS_HPP_
#define _STOCKS_HPP_
struct stock
{
std::string code;
std::string name;
double open_price;
double high_price;
double low_price;
double last_price;
double buy_price;
int buy_quantity;
double sell_price;
int sell_quantity;
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & code;
ar & name;
ar & open_price;
ar & high_price;
ar & low_price;
ar & last_price;
ar & buy_price;
ar & buy_quantity;
ar & sell_price;
ar & sell_quantity;
}
};
#endif
You have written Error code = 125. Message: Operation canceled as comment in previous response, i think that socket may be closed before async operation will be done.
What is lifetime of your socket ?
[1] socket is created in Accept method
std::shared_ptr<asio::ip::tcp::socket>
sock(new asio::ip::tcp::socket(m_ios)); // ref count +1
//...
(new Service)->StartHandligClient(sock); // this function returns immediately
// so socket's ref count -1
[2] in StartHandligClient()
sock is passed by value, so ref count of socket +1, but
void StartHandligClient(
std::shared_ptr<asio::ip::tcp::socket> sock) { // +1 ref count
read_async_1(sock); // this function returns immediately
return; // -1 ref count of socket
}
[3] in read_async_1 socket is passed by value, +1 on ref count of socket, but this function returns immediately, when function ends, ref count is decreased and socket object is deleted.
You created lambda object to execute asynchronus operation, but socket object may be closed before doing it.
You did apparently use a asio::io_service, but you forgot to run it.
m_ios.run();
Run the io_context object's event processing loop.
Fix this and your handler[s] will be called.
You can either create a thread for this, or call it in your main function in your 'main-thread'.
std::thread([this]() { m_ios.run(); } );
Note: Don't forget to stop (1) it later and join the thread (2) if you created one.

File transfer via boost tcp

I want to transfer some files via tcp over lan, and so I wrote the following code for my TX-Part:
void send_data(char * filename, char * dest)
{
try
{
boost::asio::io_service io_service;
char dest_t = *dest;
std::string adr = ip_adr_display[0] + ':' + boost::lexical_cast<std::string>(PORTNUM_TCP_IN);
std::cout << "Adress is: " << adr << " and file is: " << filename << '\n';
if(debugmode)
debug_global << adr << '\n';
std::string file = filename;
async_tcp_client client(io_service, adr, file);
io_service.run();
}
catch(std::exception& e)
{
};
};
and RX-Part:
void rec_data(void)
{
try
{
std::cout << "Receiving data...\n";
async_tcp_server *recv_file_tcp_server = new async_tcp_server(PORTNUM_TCP_IN);
if(debugmode)
debug_global << "Receiving...\n";
delete recv_file_tcp_server;
}
catch(std::exception &e)
{
};
};
with the following server and client code:
using boost::asio::ip::tcp;
class async_tcp_client
{
public:
async_tcp_client(boost::asio::io_service& io_service, const std::string& server, const std::string& path):resolver_(io_service), socket_(io_service)
{
size_t pos = server.find(':');
if(pos==std::string::npos)
return;
std::string port_string = server.substr(pos+1);
std::string server_ip_or_host = server.substr(0,pos);
source_file.open(path.c_str(), std::ios_base::binary|std::ios_base::ate);
if(!source_file)
{
std::cout << "Failed to open " << path << std::endl;
return;
}
size_t file_size = source_file.tellg();
source_file.seekg(0);
std::ostream request_stream(&request_);
request_stream << path << "\n" << file_size << "\n\n";
std::cout << "Request size: " << request_.size() << std::endl;
tcp::resolver::query query(server_ip_or_host, port_string);
resolver_.async_resolve(query, boost::bind(&async_tcp_client::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
};
private:
void handle_resolve(const boost::system::error_code & err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
std::cout << "Error: " << err.message() << '\n';
}
};
void handle_connect(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
boost::asio::async_write(socket_, request_, boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
}
else if(endpoint_iterator != tcp::resolver::iterator())
{
socket_.close();
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
std::cout << "Error: " << err.message() << '\n';
};
}
void handle_write_file(const boost::system::error_code& err)
{
if(!err)
{
if(source_file.eof() == false)
{
source_file.read(buf.c_array(), (std::streamsize)buf.size());
if(source_file.gcount()<= 0)
{
std::cout << "read file error" << std::endl;
return;
};
std::cout << "Send " << source_file.gcount() << "bytes, total: " << source_file.tellg() << " bytes.\n";
boost::asio::async_write(socket_, boost::asio::buffer(buf.c_array(), source_file.gcount()),boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
if(err)
{
std::cout << "Send error: " << err << std::endl;
return;
}
}
else
return;
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
};
tcp::resolver resolver_;
tcp::socket socket_;
boost::array<char, 1024> buf;
boost::asio::streambuf request_;
std::ifstream source_file;
};
class async_tcp_connection: public boost::enable_shared_from_this<async_tcp_connection>
{
public:
async_tcp_connection(boost::asio::io_service& io_service):socket_(io_service), file_size(0){}
void start()
{
if(debugmode)
debug_global << __FUNCTION__ << std::endl;
async_read_until(socket_, request_buf, "\n\n", boost::bind(&async_tcp_connection::handle_read_request, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
boost::asio::ip::tcp::socket& socket(){return socket_;}
private:
boost::asio::streambuf request_buf;
size_t file_size;
std::ofstream output_file;
boost::asio::ip::tcp::socket socket_;
boost::array<char, 40960> buf;
void handle_read_request(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if(err)
{
return handle_error(__FUNCTION__, err);
}
if(debugmode)
debug_global << __FUNCTION__ << "(" << bytes_transferred << ")" <<", in_avail = " << request_buf.in_avail() << ", size = " << request_buf.size() << ", max_size = " << request_buf.max_size() << ".\n";
std::istream request_stream(&request_buf);
std::string file_path;
request_stream >> file_path;
request_stream >> file_size;
request_stream.read(buf.c_array(), 2);
if(debugmode)
debug_global << file_path << " size is " << file_size << ", tellg = " << request_stream.tellg() << std::endl;
size_t pos = file_path.find_last_of('\\');
if(pos!= std::string::npos)
file_path = file_path.substr(pos+1);
output_file.open(file_path.c_str(), std::ios_base::binary);
if(!output_file)
{
if(debugmode)
debug_global << "Failed to open: " << file_path << std::endl;
return;
}
do{
request_stream.read(buf.c_array(), (std::streamsize)buf.size());
if(debugmode)
debug_global << __FUNCTION__ << " write " << request_stream.gcount() << " bytes.\n";
output_file.write(buf.c_array(), request_stream.gcount());
}while(request_stream.gcount() > 0);
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()),boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_read_file_content(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (bytes_transferred>0)
{
output_file.write(buf.c_array(), (std::streamsize)bytes_transferred);
if(debugmode)
debug_global << __FUNCTION__ << " recv " << output_file.tellp() << " bytes."<< std::endl;
if (output_file.tellp()>=(std::streamsize)file_size)
{
return;
}
}
if (err)
{
return handle_error(__FUNCTION__, err);
}
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()), boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_error(const std::string& function_name, const boost::system::error_code& err)
{
if(debugmode)
debug_global << __FUNCTION__ << " in " << function_name <<" due to " << err <<" " << err.message()<< std::endl;
}
};
class async_tcp_server : private boost::noncopyable
{
public:
typedef boost::shared_ptr<async_tcp_connection> ptr_async_tcp_connection;
async_tcp_server(unsigned short port):acceptor_(io_service_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), true)
{
ptr_async_tcp_connection new_connection_(new async_tcp_connection(io_service_));
acceptor_.async_accept(new_connection_->socket(), boost::bind(&async_tcp_server::handle_accept, this,new_connection_, boost::asio::placeholders::error));
io_service_.run();
}
void handle_accept(ptr_async_tcp_connection current_connection, const boost::system::error_code& e)
{
if(debugmode)
debug_global << __FUNCTION__ << " " << e << ", " << e.message()<<std::endl;
if (!e)
{
current_connection->start();
//ptr_async_tcp_connection new_connection_(new async_tcp_connection(io_service_));
//acceptor_.async_accept(new_connection_->socket(),
// boost::bind(&async_tcp_server::handle_accept, this,new_connection_,
// boost::asio::placeholders::error));
}
}
~async_tcp_server()
{
io_service_.stop();
}
private:
boost::asio::io_service io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
};
If I want to transmit a file, I have to enter the absolute path (why?), if I enter the relative path (e.g. "Image.jpg"), I get the error message "failed to open Image.jpg".
After successfull calling the function, I get the following output:
Adress is: <ip>:<port> and file is: <full file path>
Request size: 91
Send 1024 bytes, total: 1024 bytes
Send 1024 bytes, total: 2048 bytes
etc..
Send 1024 bytes, total: 20480 bytes
Send 406 bytes, total: -1 bytes (Why?)
At the receiving side, I get no received data. Why? I do not understand why my code is not working...
Thank you very much!
UPDATE In my answer I casually said
I added postfix .received to the output file name to prevent overwriting the source.
I just realized that this is likely your problem:
If you use your code with the receiver on the same machine as the sender, you will overwrite the source file while you are still sending it... OOPS.
So, I fixed up the code just I could run it.
Here's the test main:
int main()
{
boost::thread_group g;
g.create_thread(rec_data); // get the receiver running
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
g.create_thread([] { send_data("test.cpp"); });
g.join_all();
}
I added postfix .received to the output file name to prevent overwriting the source.
When running this, it appears to work reasonably well:
g++ -std=c++11 -Wall -pedantic -pthread test.cpp -lboost_system -lboost_thread
./a.out
md5sum test.cpp test.cpp.received
We get the output
0dc16e7f0dc23cb9fce100d825852621 test.cpp.received
0dc16e7f0dc23cb9fce100d825852621 test.cpp
I've also tested it with a png and with a 93Mb executable.
Full code (also on Coliru, although Coliru doesn't allow network connections):
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <fstream>
#include <boost/enable_shared_from_this.hpp>
using boost::asio::ip::tcp;
static bool debugmode = true;
static boost::mutex debug_mutex;
static std::ostream debug_global(std::clog.rdbuf());
class async_tcp_client
{
public:
async_tcp_client(boost::asio::io_service& io_service, const std::string& server, const std::string& path)
: resolver_(io_service), socket_(io_service)
{
size_t pos = server.find(':');
if(pos==std::string::npos)
{
return;
}
std::string port_string = server.substr(pos+1);
std::string server_ip_or_host = server.substr(0,pos);
source_file.open(path, std::ios_base::binary|std::ios_base::ate);
if(!source_file)
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << __LINE__ << "Failed to open " << path << std::endl;
return;
}
size_t file_size = source_file.tellg();
source_file.seekg(0);
std::ostream request_stream(&request_);
request_stream << path << "\n" << file_size << "\n\n";
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Request size: " << request_.size() << std::endl;
}
tcp::resolver::query query(server_ip_or_host, port_string);
resolver_.async_resolve(query, boost::bind(&async_tcp_client::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
};
private:
void handle_resolve(const boost::system::error_code & err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Error: " << err.message() << '\n';
}
};
void handle_connect(const boost::system::error_code &err, tcp::resolver::iterator endpoint_iterator)
{
if(!err)
{
boost::asio::async_write(socket_, request_, boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
}
else if(endpoint_iterator != tcp::resolver::iterator())
{
socket_.close();
tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint, boost::bind(&async_tcp_client::handle_connect, this, boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Error: " << err.message() << '\n';
};
}
void handle_write_file(const boost::system::error_code& err)
{
if(!err)
{
if(source_file)
//if(source_file.eof() == false)
{
source_file.read(buf.c_array(), (std::streamsize)buf.size());
if(source_file.gcount()<= 0)
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "read file error" << std::endl;
return;
};
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Send " << source_file.gcount() << "bytes, total: " << source_file.tellg() << " bytes.\n";
}
boost::asio::async_write(socket_, boost::asio::buffer(buf.c_array(), source_file.gcount()),boost::bind(&async_tcp_client::handle_write_file, this, boost::asio::placeholders::error));
}
else
{
return;
}
}
else
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Error: " << err.message() << "\n";
}
};
tcp::resolver resolver_;
tcp::socket socket_;
boost::array<char, 1024> buf;
boost::asio::streambuf request_;
std::ifstream source_file;
};
class async_tcp_connection: public boost::enable_shared_from_this<async_tcp_connection>
{
public:
async_tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service), file_size(0)
{
}
void start()
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << std::endl;
}
async_read_until(socket_, request_buf, "\n\n", boost::bind(&async_tcp_connection::handle_read_request, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
boost::asio::ip::tcp::socket& socket()
{
return socket_;
}
private:
boost::asio::streambuf request_buf;
std::ofstream output_file;
boost::asio::ip::tcp::socket socket_;
size_t file_size;
boost::array<char, 40960> buf;
void handle_read_request(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if(err)
{
return handle_error(__FUNCTION__, err);
}
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << "(" << bytes_transferred << ")" <<", in_avail = " << request_buf.in_avail() << ", size = " << request_buf.size() << ", max_size = " << request_buf.max_size() << ".\n";
}
std::istream request_stream(&request_buf);
std::string file_path;
request_stream >> file_path;
request_stream >> file_size;
request_stream.read(buf.c_array(), 2);
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << file_path << " size is " << file_size << ", tellg = " << request_stream.tellg() << std::endl;
}
size_t pos = file_path.find_last_of('\\');
if(pos!= std::string::npos)
{
file_path = file_path.substr(pos+1);
}
output_file.open(file_path + ".received", std::ios_base::binary);
if(!output_file)
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __LINE__ << "Failed to open: " << file_path << std::endl;
}
return;
}
do
{
request_stream.read(buf.c_array(), (std::streamsize)buf.size());
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " write " << request_stream.gcount() << " bytes.\n";
}
output_file.write(buf.c_array(), request_stream.gcount());
}
while(request_stream.gcount() > 0);
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()),boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_read_file_content(const boost::system::error_code& err, std::size_t bytes_transferred)
{
if (bytes_transferred>0)
{
output_file.write(buf.c_array(), (std::streamsize)bytes_transferred);
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " recv " << output_file.tellp() << " bytes."<< std::endl;
}
if (output_file.tellp()>=(std::streamsize)file_size)
{
return;
}
}
if (err)
{
return handle_error(__FUNCTION__, err);
}
async_read(socket_, boost::asio::buffer(buf.c_array(), buf.size()), boost::bind(&async_tcp_connection::handle_read_file_content, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void handle_error(const std::string& function_name, const boost::system::error_code& err)
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " in " << function_name <<" due to " << err <<" " << err.message()<< std::endl;
}
}
};
class async_tcp_server : private boost::noncopyable
{
public:
typedef boost::shared_ptr<async_tcp_connection> ptr_async_tcp_connection;
async_tcp_server(unsigned short port):acceptor_(io_service_, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), true)
{
ptr_async_tcp_connection new_connection_(new async_tcp_connection(io_service_));
acceptor_.async_accept(new_connection_->socket(), boost::bind(&async_tcp_server::handle_accept, this,new_connection_, boost::asio::placeholders::error));
io_service_.run();
}
void handle_accept(ptr_async_tcp_connection current_connection, const boost::system::error_code& e)
{
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << __FUNCTION__ << " " << e << ", " << e.message()<<std::endl;
}
if (!e)
{
current_connection->start();
}
}
~async_tcp_server()
{
io_service_.stop();
}
private:
boost::asio::io_service io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
};
void send_data(std::string const& filename, std::string const& adr = "localhost:6767")
{
try
{
boost::asio::io_service io_service;
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Adress is: " << adr << " and file is: " << filename << '\n';
}
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << adr << '\n';
}
async_tcp_client client(io_service, adr, filename);
io_service.run();
}
catch(std::exception const& e)
{
std::cerr << "Exception in " << __PRETTY_FUNCTION__ << ": " << e.what() << "\n";
};
};
void rec_data(void)
{
try
{
{
boost::mutex::scoped_lock lk(debug_mutex);
std::cout << "Receiving data...\n";
}
async_tcp_server recv_file_tcp_server(6767);
if(debugmode)
{
boost::mutex::scoped_lock lk(debug_mutex);
debug_global << "Received\n";
}
}
catch(std::exception const& e)
{
std::cerr << "Exception in " << __PRETTY_FUNCTION__ << ": " << e.what() << "\n";
};
};
int main()
{
boost::thread_group g;
g.create_thread(rec_data); // get the receiver running
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
g.create_thread([] { send_data("main.cpp"); });
g.join_all();
}

boost asio issue within the loop

Could someone help me with folowing questions?
I'm trying to call async_send within the while loop. The data is sent to server correctly but onSend handler is not called at all... If I do not use the while loop all works fine (the data is sent and received, all handlers ae called)
Will my code work correctly if we send some msgs before server's answer on previous msgs?
Here is the TCP client code
class TCPClient
{
public:
static const size_t maxBufLen = 100;
static const size_t MAX_INPUT_SIZE = 10;
TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter);
void close();
private:
boost::asio::io_service& m_IOService;
tcp::socket m_Socket;
char recieveBuffer[maxBufLen];
void promptTxMsgLoop();
void onConnect(const boost::system::error_code& ErrorCode, tcp::resolver::iterator EndPointIter);
void onReceive(const boost::system::error_code& ErrorCode);
void onSend(const boost::system::error_code& ErrorCode);
void doClose();
};
TCPClient::TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter)
: m_IOService(IO_Service), m_Socket(IO_Service)
{
tcp::endpoint EndPoint = *EndPointIter;
recieveBuffer[0] = '\0';
m_Socket.async_connect(EndPoint,
boost::bind(&TCPClient::onConnect, this, boost::asio::placeholders::error, ++EndPointIter));
}
void TCPClient::onConnect(const boost::system::error_code& ErrorCode, tcp::resolver::iterator EndPointIter)
{
if (ErrorCode == 0)
{
this->promptTxMsgLoop();
}
else if (EndPointIter != tcp::resolver::iterator())
{
cout << "m_Socket.close();!" << endl;
m_Socket.close();
tcp::endpoint EndPoint = *EndPointIter;
m_Socket.async_connect(EndPoint,
boost::bind(&TCPClient::onConnect, this, boost::asio::placeholders::error, ++EndPointIter));
}
}
void TCPClient::promptTxMsgLoop()
{
recieveBuffer[0] = '\0';
while (true)
{
cout << "> " ;
string tmp;
cin >> tmp;
cout << "Entered: " << tmp << endl;
tmp += "\0";
if (tmp.length() < MAX_INPUT_SIZE-1)
{
try
{
//lock untill buffer is emty
while (strlen(recieveBuffer) > 1)
{
}
//onSend handler is never is called inside while loop
m_Socket.async_send(boost::asio::buffer(tmp.c_str(),tmp.length()+1),
boost::bind(&TCPClient::onSend, this, boost::asio::placeholders::error));
}
catch(exception &e)
{
cerr << "Cannot add msg to send queue... " << e.what() << endl;
}
}
else
cout << "Error: input string is too long. Max length is " << MAX_INPUT_SIZE-1 << endl;
}
}
void TCPClient::onSend(const boost::system::error_code& ErrorCode)
{
cout << "Msg has been sent..." << endl;
if (strlen(recieveBuffer) > 1)
cout << "ERROR: recieveBuffer in not epmty. Data is overritten!" << endl;
if (!ErrorCode)
{
m_Socket.async_receive(boost::asio::buffer(recieveBuffer, TCPClient::maxBufLen),
boost::bind(&TCPClient::onReceive, this, boost::asio::placeholders::error));
}
else
{
cout << "onSend closing" << endl;
cout << "ERROR! onSend..." << ErrorCode << endl;
doClose();
}
}
void TCPClient::onReceive(const boost::system::error_code& ErrorCode)
{
cout << "Msg has been received..." << endl;
if (ErrorCode == 0)
{
cout << recieveBuffer << endl;
cout << "msg length: " << strlen(recieveBuffer) << endl;
//unlock buffer
recieveBuffer[0] = '\0';
}
else
{
cout << "ERROR! onReceive..." << ErrorCode << endl;
doClose();
}
}
void TCPClient::doClose()
{
m_Socket.close();
}
int main()
{
try
{
boost::asio::io_service IO_Service;
tcp::resolver Resolver(IO_Service);
tcp::resolver::query Query("127.0.0.1", "1");
tcp::resolver::iterator EndPointIterator = Resolver.resolve(Query);
TCPClient Client(IO_Service, EndPointIterator);
boost::thread ClientThread(boost::bind(&boost::asio::io_service::run, &IO_Service));
ClientThread.join();
Client.close();
}
catch (exception& e)
{
cerr << e.what() << endl;
}
cout << "\nClosing";
getch();
}
In 'onConnect' completion-handler you call promptTxMsgLoop that performs an infinite while loop, so you actually never let io_service to continue its work -- thus no completion handlers will be invoked anymore.
Besides, you call async_send multiple times, without waiting for the comletion handler of the previous async_send, which is also incorrect.
Please, see asio documentation to find out correct use patterns.