boost::asio io_service::run_one leads to a Segmentation fault - c++

I am implementing a class that uses boost::asio to implement a library for TLS connections.
I am implementing only synchronous operations and some of them accept a timeout. I implement the timeout methods using a deadline_timer and io_service.run_one, as explained in this example: http://www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/example/timeouts/async_tcp_client.cpp
My problem is with a method that reads exactly 'n' bytes from the socket and accepts a timeout as a parameter. The problem is that the io_service.run_one() is raising a SIGSEV and I do no know why. Below is the code (it is so long, but I do not know any other better way to explain this):
The code
Below are the methods involved in the test I am executing:
void CMDRboostConnection::check_deadline()
{
// Check whether the deadline has passed. We compare the deadline against
// the current time since a new asynchronous operation may have moved the
// deadline before this actor had a chance to run.
if (m_timeoutOpsTimer->expires_at() <= boost::asio::deadline_timer::traits_type::now())
{
// TODO do I need to cancel async operations?
m_timeoutOpsErrorCode = boost::asio::error::timed_out;
// There is no longer an active deadline. The expiry is set to positive
// infinity so that the actor takes no action until a new deadline is set.
m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin);
}
// Put the actor back to sleep.
m_timeoutOpsTimer->async_wait(
boost::bind(&CMDRboostConnection::check_deadline, this));
}
bool CMDRboostConnection::connect()
{
// TODO: This method already throws an exception, it should be void.
DEBUG("Connecting to " + m_url + " : " + m_port);
try
{
// If the socket is already connected, disconnect it before
// opening a new conneciont.
if (isConnected())
{
disconnect();
}
m_socket = new SSLSocket(m_ioService, m_context);
tcp::resolver resolver(m_ioService);
tcp::resolver::query query(m_url, m_port);
tcp::resolver::iterator end;
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
boost::asio::connect(m_socket->lowest_layer(), resolver.resolve(query));
if (endpoint_iterator == end)
{
DEBUG("Endpoint cannot be resolved, disconnecting...");
disconnect();
}
else
{
m_timeoutOpsTimer = new boost::asio::deadline_timer(m_ioService);
m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin);
// Start the persistent actor that checks for deadline expiry.
check_deadline();
DEBUG("Endpoint resolved, performing handshake");
m_socket->set_verify_mode(boost::asio::ssl::verify_none);
m_socket->handshake(SSLSocket::client);
DEBUG("Handshake done, connected to " + m_url + " : " + m_port);
m_isConnected = true;
}
}
catch (boost::system::system_error &err)
{
disconnect();
throw;
}
return m_isConnected;
}
std::streambuf& CMDRboostConnection::readNBytes(int n, unsigned int timeout)
{
try
{
if(!isConnected())
{
std::string err = "Cannot read, not connected";
ERROR(err);
throw std::logic_error(err);
}
if(n == 0)
{
return m_buffer;
}
m_timeoutOpsTimer->expires_from_now(
boost::posix_time::milliseconds(timeout));
m_timeoutOpsErrorCode = boost::asio::error::would_block;
boost::asio::async_read(
*m_socket,
m_buffer,
boost::asio::transfer_exactly(n),
boost::bind(
&CMDRboostConnection::timoutOpsCallback,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)
);
do
{
m_ioService.run_one();
} while (m_timeoutOpsErrorCode == boost::asio::error::would_block);
if(m_timeoutOpsErrorCode)
{
throw boost::system::system_error(m_timeoutOpsErrorCode);
}
return m_buffer;
}
catch(boost::system::system_error &err)
{
ERROR("Timeout reached trying to read a message");
disconnect();
throw;
}
}
void CMDRboostConnection::disconnect()
{
try
{
DEBUG("Disconnecting...");
if(isConnected())
{
m_socket->shutdown();
DEBUG("Closing socket...");
m_socket->lowest_layer().close();
if(m_socket != NULL)
{
delete m_socket;
m_socket = NULL;
}
}
if(m_timeoutOpsTimer != NULL)
{
delete m_timeoutOpsTimer;
m_timeoutOpsTimer = NULL;
}
DEBUG("Disconnection performed properly");
m_isConnected = false;
}
catch (boost::system::system_error &err)
{
ERROR("Exception thrown, error = " << err.code() <<
", category: " << err.code().category().name() << std::endl);
m_isConnected = false;
throw;
}
}
The test
Below it is the test I am running to test the method:
TEST(CMDRboostConnection, readNbytesTimeoutDoesNotMakeTheProgramCrashWhenTmeout)
{
std::auto_ptr<CMDR::SSL::ICMDRsslConnection> m_connection =
std::auto_ptr<CMDR::SSL::ICMDRsslConnection>(
new CMDR::SSL::CMDRboostConnection("localhost", "9999"));
unsigned int sleepInterval = 0; // seconds
unsigned int timeout = 10; // milliseconds
unsigned int numIterations = 10;
std::string msg("delay 500000"); // microseconds
if(!m_connection->isConnected())
{
m_connection->connect();
}
for(unsigned int i = 0; i < numIterations; i++)
{
if(!m_connection->isConnected())
{
m_connection->connect();
}
ASSERT_NO_THROW( m_connection->write(msg) );
ASSERT_THROW (
m_connection->readNBytes(msg.size(), timeout),
boost::system::system_error);
ASSERT_FALSE(m_connection->isConnected());
ASSERT_NO_THROW( m_connection->connect() );
sleep(sleepInterval);
}
}
The problem
In the above test, the first loop iteration goes ok, that is, the first time the method readNBytes is called, it works (throws an exception as expected). The second time it is executed, it raises the SIGSEV.
EDIT
I am executing the above test among others that test other functionalities. I have realized that if I execute the above test only, it works. But, If I execute it in addition other, then the program crashes with the mentioned SIGSEV.
This is one of the tests that causes the problem:
TEST(CMDRboostConnection, canConnectDisconnect)
{
std::auto_ptr<CMDR::SSL::ICMDRsslConnection> m_connection =
std::auto_ptr<CMDR::SSL::ICMDRsslConnection>(
new CMDR::SSL::CMDRboostConnection("localhost", "9999"));
unsigned int sleepInterval = 0; // seconds
unsigned int timeout = 1000; // milliseconds
unsigned int numIterations = 10;
std::string msg("normally");
if(!m_connection->isConnected())
{
ASSERT_NO_THROW (m_connection->connect() );
}
for(unsigned int i = 0; i < numIterations; i++)
{
ASSERT_NO_THROW( m_connection->disconnect() );
sleep(sleepInterval);
ASSERT_NO_THROW( m_connection->connect() );
}
}
In conclusion, If I execute both the above tests, the first one crashes. But if I execute only the first one, it works.
EDIT 2
Fixed the bug mentioned in the comments.

You've messed up pointers and object lifetime management. If connect method is called when already connected you overwrite old socket with new and only then check whether it was already connected or used somewhere. Also auto_ptr is deprecated. You should use unique_ptr to manage owning pointers instead.

I have replaced all the member attributes by pointers, and now it works (that is, I can pass all tests I have write). The methods disconnect / connect are now as following:
bool CMDRboostConnection::connect()
{
// TODO: This method already throws an exception, it should be void.
DEBUG("Connecting to " + m_url + " : " + m_port);
try
{
// If the socket is already connected, disconnect it before
// opening a new conneciont.
if (isConnected())
{
disconnect();
}
m_ioService = new boost::asio::io_service();
m_timeoutOpsTimer = new boost::asio::deadline_timer(*m_ioService);
m_context = new boost::asio::ssl::context(boost::asio::ssl::context::sslv23);
m_socket = new SSLSocket(*m_ioService, *m_context);
tcp::resolver resolver(*m_ioService);
tcp::resolver::query query(m_url, m_port);
tcp::resolver::iterator end;
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
boost::asio::connect(m_socket->lowest_layer(), resolver.resolve(query));
if (endpoint_iterator == end)
{
DEBUG("Endpoint cannot be resolved, disconnecting...");
disconnect();
}
else
{
m_timeoutOpsTimer->expires_at(boost::posix_time::pos_infin);
// Start the persistent actor that checks for deadline expiry.
check_deadline();
DEBUG("Endpoint resolved, performing handshake");
m_socket->set_verify_mode(boost::asio::ssl::verify_none);
m_socket->handshake(SSLSocket::client);
DEBUG("Handshake done, connected to " + m_url + " : " + m_port);
m_isConnected = true;
}
}
catch (boost::system::system_error &err)
{
disconnect();
throw;
}
return m_isConnected;
}
void CMDRboostConnection::disconnect()
{
try
{
DEBUG("Disconnecting...");
if(isConnected())
{
m_socket->shutdown();
DEBUG("Closing socket...");
m_socket->lowest_layer().close();
if(m_socket != NULL)
{
delete m_socket;
m_socket = NULL;
}
}
if(m_timeoutOpsTimer != NULL)
{
delete m_timeoutOpsTimer;
m_timeoutOpsTimer = NULL;
}
if(m_context != NULL)
{
delete m_context;
m_context = NULL;
}
if(m_ioService != NULL)
{
delete m_ioService;
m_ioService = NULL;
}
DEBUG("Disconnection performed properly");
m_isConnected = false;
}
catch (boost::system::system_error &err)
{
ERROR("Exception thrown, error = " << err.code() <<
", category: " << err.code().category().name() << std::endl);
if(m_timeoutOpsTimer != NULL)
{
delete m_timeoutOpsTimer;
m_timeoutOpsTimer = NULL;
}
if(m_context != NULL)
{
delete m_context;
m_context = NULL;
}
if(m_ioService != NULL)
{
delete m_ioService;
m_ioService = NULL;
}
m_isConnected = false;
throw;
}
}
As you can see, now the socket, the io_service, the deadline_timer and the context are created on connecting and released on disconnecting. I still do not understand what is going on, let me explain:
I have tried to reimplement the above variables one next one, that is, first the socket, then the timer, then the context and finally the io_service.
The tests have passed only when the io_service is a ptr, but I can't understand why. If io_service is a class-scoped variable, it should be deleted every time the class instance goes out of scope, that is, every time one of my TESTs finishes.
It seems that, before implementing it as a ptr, that was not happening. I suspect that maybe, when the readNBytes throws an exception due to a timeout, the read_async call remains in the io_service action queue, and maybe that caused the problem.

Related

boost::asio - the acceptor doesn't call the callback after the server is stopped and started again

I've created a simple wrapper for boost::asio library. My wrapper consists of 4 main classes: NetServer (server), NetClient (client), NetSession (client/server session) and Network (composition class of these three which also includes all callback methods).
The problem is that the first connection client/server works flawlessly, but when I then stop server, start it again and then try to connect the client, the server just doesn't recognize the client. It seems like the acceptor callback isn't called. And client does connect to server, because first - the connection goes without errors, second - when I close the server's program, the client receives the error message WSAECONNRESET.
I've created test program which emulates the procedure written above. It does following:
Starts the server
Starts the client
Client succesfully connects to server
Stops the server
Client receives the error and disconnects itself
Starts the server again
Client again succesfully connects to server
BUT SERVER DOESN'T CALL THE ACCEPTOR CALLBACK ANYMORE
It means that in point 3 the acceptor succesfully calls the callback function, but in point 7 the acceptor doesn't call the callback.
I think I do something wrong in stop()/start() method of the server, but I can't figure out what's exactly wrong.
The source of the NetServer class:
NetServer::NetServer(Network& netRef) : net{ netRef }
{
acceptor = std::make_unique<boost::asio::ip::tcp::acceptor>(ioc);
}
NetServer::~NetServer(void)
{
ioc.stop();
if (threadStarted)
{
th.join();
threadStarted = false;
}
if (active)
stop();
}
int NetServer::start(void)
{
assert(getAcceptHandler() != nullptr);
assert(getHeaderHandler() != nullptr);
assert(getDataHandler() != nullptr);
assert(getErrorHandler() != nullptr);
closeAll();
try
{
ep = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), srvPort);
acceptor->open(ep.protocol());
acceptor->bind(ep);
acceptor->listen();
initAccept();
}
catch (system::system_error& e)
{
return e.code().value();
}
if (!threadStarted)
{
th = std::thread([this]()
{
ioc.run();
});
threadStarted = true;
}
active = true;
return Network::NET_OK;
}
int NetServer::stop(void)
{
ioc.post(boost::bind(&NetServer::_stop, this));
return Network::NET_OK;
}
void NetServer::_stop(void)
{
boost::system::error_code ec;
acceptor->close(ec);
for (auto& s : sessions)
closeSession(s.get(), false);
active = false;
}
void NetServer::initAccept(void)
{
sock = std::make_shared<asio::ip::tcp::socket>(ioc);
acceptor->async_accept(*sock.get(), [this](const boost::system::error_code& error)
{
onAccept(error, sock);
});
}
void NetServer::onAccept(const boost::system::error_code& ec, SocketSharedPtr sock)
{
if (ec.value() == 0)
{
if (accHandler())
{
addSession(sock);
initAccept();
}
}
else
getErrorHandler()(nullptr, ec);
}
SessionPtr NetServer::addSession(SocketSharedPtr sock)
{
std::lock_guard<std::mutex> guard(mtxSession);
auto session = std::make_shared<NetSession>(sock, *this, true);
sessions.insert(session);
session->start();
return session;
}
SessionPtr NetServer::findSession(const SessionPtr session)
{
for (auto it = std::begin(sessions); it != std::end(sessions); it++)
if (*it == session)
return *it;
return nullptr;
}
bool NetServer::closeSession(const void *session, bool erase /* = true */)
{
std::lock_guard<std::mutex> guard(mtxSession);
for (auto it = std::begin(sessions); it != std::end(sessions); it++)
if (it->get() == session)
{
try
{
it->get()->getSocket()->cancel();
it->get()->getSocket()->shutdown(asio::socket_base::shutdown_send);
it->get()->getSocket()->close();
it->get()->getSocket().reset();
}
catch (system::system_error& e)
{
UNREFERENCED_PARAMETER(e);
}
if (erase)
sessions.erase(*it);
return true;
}
return false;
}
void NetServer::closeAll(void)
{
using namespace boost::placeholders;
std::lock_guard<std::mutex> guard(mtxSession);
std::for_each(sessions.begin(), sessions.end(), boost::bind(&NetSession::stop, _1));
sessions.clear();
}
bool NetServer::write(const SessionPtr session, std::string msg)
{
if (SessionPtr s = findSession(session); s)
{
s->addMessage(msg);
if (s->canWrite())
s->write();
return true;
}
return false;
}
This is the output from the server:
Enter 0 - server, 1 - client: 0
1. Server started
3. Client connected to server
Stopping server....
4. Server stopped
Net error, server, acceptor: ERROR_OPERATION_ABORTED
Net error, server, ERROR_OPERATION_ABORTED
Client session deleted
6. Server started again
(HERE SHOULD BE "8. Client again connected to server", but the server didn't recognize the reconnected client!)
And from the client:
Enter 0 - server, 1 - client: 1
2. Client started and connected to server
Net error, client: ERROR_FILE_NOT_FOUND
5. Client disconnected from server
Waiting 3 sec before reconnect...
Connecting to server...
7. Client started and connected to server
(WHEN I CLOSE THE SERVER WINDOW, I RECVEIVE HERE THE "Net error, client: WSAECONNRESET" MESSAGE - it means client was connected to server anyhow!)
If the code of NetClient, NetSession and Network is necessary, just let me know.
Thanks in advance
Wow. There's a lot to unpack. There is quite a lot of code smell that reminds me of some books on Asio programming that turned out to be... not excellent in my previous experience.
I couldn't give any real advice without grokking your code, which requires me to review in-depth and add missing bits. So let me just provide you with my reviewed/fixed code first, then we'll talk about some of the details.
A few areas where you seemed to have trouble making up your mind:
whether to use a strand or to use mutex locking
whether to use async or sync (e.g. closeSession is completely synchronous and blokcing)
whether to use shared-pointers for lifetime or not: on the one hand you have NetSesion support shared_from_this, but on the other hand you are keeping them alive in a sessions collection.
whether to use smart pointers or raw pointers (sp.get() is a code smell)
whether to use void* pointers or forward declared structs for opaque implementation
whether to use exceptions or to use error codes. Specifically:
return e.code().value();
is a Very Bad Idea. Just return error_code already. Or just propagate the exception.
judging from the use, my best bet is that sessions is std::set<SessionPtr>. Then it's funny that you're doing linear searches. In fact, findSession could be:
SessionPtr findSession(SessionPtr const& session) {
std::lock_guard guard(mtxSessions);
return sessions.contains(session)? session: nullptr;
}
In fact, given some natural invariants, it could just be
auto findSession(SessionPtr s) { return std::move(s); }
Note as well, you had forgotten to lock the mutex in findSession
closeSession completely violates Law Of Demeter, 6*3 times over if you will. In my example I make it so SessHandle is a weak pointer to NetSession and you can just write:
for (auto& handle : sessions)
if (auto sess = handle.lock())
sess->close();
Of course, sess->close() should not block
Also, it should correctly synchronize on the session e.g. using the sessions strand:
void close() {
return post(sock_.get_executor(), [this, self = shared_from_this()] {
error_code ec;
if (!ec) sock_.cancel(ec);
if (!ec) sock_.shutdown(tcp::socket::shutdown_send, ec);
if (!ec) sock_.close(ec);
});
}
If you insist, you can make it so the caller can still await the result and receive any exceptions:
std::future<void> close() {
return post(
sock_.get_executor(),
std::packaged_task<void()>{[this, self = shared_from_this()] {
sock_.cancel();
sock_.shutdown(tcp::socket::shutdown_send);
sock_.close();
}});
}
Honestly, that seems overkill since you never look at the return value anyways.
In general, I recommend leaving socket::close() to the destructor. It avoids a specific class of race-conditions on socket handles.
Don't use boolean flags (isThreadActive is better replaced with th.joinable())
apparently you had NetSession::stop which I imagine did largely the same as closeSession but in the right place? I replaced it with the new NetSession::close
subtly when accHandler returned false, you would exit the accept loop alltogether. I doubt that was on purpose
try to minimize time under locks:
std::future<void> close() {
return post(
sock_.get_executor(),
std::packaged_task<void()>{[this, self = shared_from_this()] {
sock_.cancel();
sock_.shutdown(tcp::socket::shutdown_send);
sock_.close();
}});
}
I will show you how to do without the lock entirely instead.
Demo Listing
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <deque>
#include <iostream>
#include <iomanip>
#include <set>
using namespace std::chrono_literals;
using namespace std::placeholders;
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
static inline std::ostream debug(std::cerr.rdbuf());
struct Network {
static constexpr error_code NET_OK{};
};
struct NetSession; // opaque forward reference
struct NetServer;
using SessHandle = std::weak_ptr<NetSession>; // opaque handle
using Sessions = std::set<SessHandle, std::owner_less<>>;
struct NetSession : std::enable_shared_from_this<NetSession> {
NetSession(tcp::socket&& s, NetServer& srv, bool)
: sock_(std::move(s))
, srv_(srv) {
debug << "New session from " << getPeer() << std::endl;
}
void start() {
post(sock_.get_executor(),
std::bind(&NetSession::do_read, shared_from_this()));
}
tcp::endpoint getPeer() const { return peer_; }
void close() {
return post(sock_.get_executor(), [this, self = shared_from_this()] {
debug << "Closing " << getPeer() << std::endl;
error_code ec;
if (!ec) sock_.cancel(ec);
if (!ec) sock_.shutdown(tcp::socket::shutdown_send, ec);
// if (!ec) sock_.close(ec);
});
}
void addMessage(std::string msg) {
post(sock_.get_executor(),
[this, msg = std::move(msg), self = shared_from_this()] {
outgoing_.push_back(std::move(msg));
if (canWrite())
write_loop();
});
}
private:
// assumed on (logical) strand
bool canWrite() const { // FIXME misnomer: shouldStartWriteLoop()?
return outgoing_.size() == 1;
}
void write_loop() {
if (outgoing_.empty())
return;
async_write(sock_, asio::buffer(outgoing_.front()),
[this, self = shared_from_this()](error_code ec, size_t) {
if (!ec) {
outgoing_.pop_front();
write_loop();
}
});
}
void do_read() {
incoming_.clear();
async_read_until(
sock_, asio::dynamic_buffer(incoming_), "\n",
std::bind(&NetSession::on_read, shared_from_this(), _1, _2));
}
void on_read(error_code ec, size_t);
tcp::socket sock_;
tcp::endpoint peer_ = sock_.remote_endpoint();
NetServer& srv_;
std::string incoming_;
std::deque<std::string> outgoing_;
};
using SessionPtr = std::shared_ptr<NetSession>;
using SocketSharedPtr = std::shared_ptr<tcp::socket>;
struct NetServer {
NetServer(Network& netRef) : net{netRef} {}
~NetServer()
{
if (acceptor.is_open())
acceptor.cancel(); // TODO seems pretty redundant
stop();
if (th.joinable())
th.join();
}
std::function<bool()> accHandler;
std::function<void(SocketSharedPtr, error_code)> errHandler;
// TODO headerHandler
std::function<void(SessionPtr, error_code, std::string)> dataHandler;
error_code start() {
assert(accHandler);
assert(errHandler);
assert(dataHandler);
closeAll(sessions);
error_code ec;
if (!ec) acceptor.open(tcp::v4(), ec);
if (!ec) acceptor.bind({{}, srvPort}, ec);
if (!ec) acceptor.listen(tcp::socket::max_listen_connections, ec);
if (!ec) {
do_accept();
if (!th.joinable()) {
th = std::thread([this] { ioc.run(); }); // TODO exceptions!
}
}
if (ec && acceptor.is_open())
acceptor.close();
return ec;
}
void stop() { //
post(ioc, std::bind(&NetServer::do_stop, this));
}
void closeSession(SessHandle handle, bool erase = true) {
post(acceptor.get_executor(), [=, this] {
if (auto s = handle.lock()) {
s->close();
}
if (erase) {
sessions.erase(handle);
}
});
}
void closeAll() {
post(acceptor.get_executor(), [this] {
closeAll(sessions);
sessions.clear();
});
}
// TODO FIXME is the return value worth it?
bool write(SessionPtr const& session, std::string msg) {
return post(acceptor.get_executor(),
std::packaged_task<bool()>{std::bind(
&NetServer::do_write, this, session, std::move(msg))})
.get();
}
// compare
void writeAll(std::string msg) {
post(acceptor.get_executor(),
std::bind(&NetServer::do_write_all, this, std::move(msg)));
}
private:
Network& net;
asio::io_context ioc;
tcp::acceptor acceptor{ioc}; // active -> acceptor.is_open()
std::thread th; // threadActive -> th.joinable()
Sessions sessions;
std::uint16_t srvPort = 8989;
// std::mutex mtxSessions; // note naming; also replaced by logical strand
// assumed on acceptor logical strand
void do_accept() {
acceptor.async_accept(
make_strand(ioc), [this](error_code ec, tcp::socket sock) {
if (ec.failed()) {
return errHandler(nullptr, ec);
}
if (accHandler()) {
auto s = std::make_shared<NetSession>(std::move(sock),
*this, true);
sessions.insert(s);
s->start();
}
do_accept();
});
}
SessionPtr do_findSession(SessionPtr const& session) {
return sessions.contains(session) ? session : nullptr;
}
bool do_write(SessionPtr session, std::string msg) {
if (auto s = do_findSession(session)) {
s->addMessage(std::move(msg));
return true;
}
return false;
}
void do_write_all(std::string msg) {
for(auto& handle : sessions)
if (auto sess = handle.lock())
do_write(sess, msg);
}
static void closeAll(Sessions const& sessions) {
for (auto& handle : sessions)
if (auto sess = handle.lock())
sess->close();
}
void do_stop()
{
if (acceptor.is_open()) {
error_code ec;
acceptor.close(ec); // TODO error handling?
}
closeAll(sessions); // TODO FIXME why not clear sessions?
}
};
// Implementation must be after NetServer definition:
void NetSession::on_read(error_code ec, size_t) {
if (srv_.dataHandler)
srv_.dataHandler(shared_from_this(), ec, std::move(incoming_));
if (!ec)
do_read();
}
int main() {
Network net;
NetServer srv{net};
srv.accHandler = [] { return true; };
srv.errHandler = [](SocketSharedPtr, error_code ec) {
debug << "errHandler: " << ec.message() << std::endl;
};
srv.dataHandler = [](SessionPtr sess, error_code ec, std::string msg) {
debug << "dataHandler: " << sess->getPeer() << " " << ec.message()
<< " " << std::quoted(msg) << std::endl;
};
srv.start();
std::this_thread::sleep_for(10s);
std::cout << "Shutdown started" << std::endl;
srv.writeAll("We're going to shutdown, take care!\n");
srv.stop();
}
Live Demo:

Boost full duplex - only client to server works, messages from server to client

I have a boost asio project that I am at wits end with. I have defined a TCPConnect class which is inherited by both a TCPSession and a TCPClient class. The reason for this is because I would like both the server and client side of the connection to be directly used for sending and receiving messages over full duplex TCP/IP. Only the TCPSession class listens for new connections, and only the TCPClient class makes an outgoing connection.
When the client connects to the server it sends a handshake message and blocks for the response. The server receives the message and sends a handshake ack back. On sending the ack, the server considers the connection completed. On receiving the ack, the client considers the connection completed.
The problem that I am having is that only the client side (the TCPClient object) can call its inherited TCPConnect::Send() and have it be received by the remote TCPSession object. If the server side (the TCPSession object) calls TCPConnect::Send(), it puts the message on the line without a problem, but the message is never received by the remote TCPClient object.
I must say I am a total beginner at working with boost. I have looked into this issue, trying to word and reword my search query, but have not found a solution. On both sides I have the TCPSession and TCPClient objects sitting on an async_read(), but on the client side the callback for the async_read() does not get called when the server side sends a message. I am assuming this has to do with how the io_service is set up on the TCPSession object's side, and possibly with threading.
So, a TCPServer is started inside a TCPWorker::run() function, this function being run in its own thread. The TCPWorker class owns the io_service object. The threaded run() function instantiates a TCPServer object and then calls io_service::run(). The TCPServicer object is responsible for creating TCPSession objects for new connections. For each new connection, the TCPServer creates a new TCPSession object with the io_service pointer and calls async_accept on its acceptor. Once a new connection is accepted, the TCPSession's socket is set for an async_read(). So it is known that io_service and the multiple sockets that can be created to use it are in one thread.
On the TCPClient side, when Connect() is called, if an io_service doesn't exist and/or a thread does not yet exist for the io_service, they are created using the following lines:
if (!m_pClientServiceThread) {
if (!m_pclient_io_service) // if no io_service has yet been created
m_pclient_io_service = new boost::asio::io_service;
m_pClientServiceThread = new boost::thread(boost::bind(&boost::asio::io_service::run, m_pclient_io_service));
}
So the service is being run in a thread. Then a new resolver and a new boost::asio::ip::tcp::socket are created given the io_service pointer, and boost::asio::connect() is called given the socket and a valid resolved endpoint. So the client seems to have its io_service running in a thread. Having successfully made the connection, I then send a handshake message using boost::asio::read(), and sit with boost::asio::read() waiting on the handshare response. One receiving a valid response, I pass the socket to an async_read() to wait for incoming messages.
I have looked at this for so long now without figuring out why the client side async_read() does no receive a message sent from the server side, even though the server side receives the message that is sent from the client side.
Please help me to figure this out. I am quite sure there is something simple I am not seeing, but as I have said, I am not a boost expert, so I am not sure what it is.
Added code:
TcpConnect class:
class TcpConnect : public boost::enable_shared_from_this<TcpConnect>
{
TcpConnect(boost::asio::io_service* pio_service, ConnType connType, std::string sHostIp, int iHostPort)
: m_pio_service(pio_service)
, eConnType(connType)
, m_strHostIp(sHostIp)
, m_iHostPort(iHostPort)
{
}
virtual ~TcpConnect() { /* does what is needed - this works */ }
bool SendBlocked(CmdPacket& msg, CmdPacket& rsp)
{
// Function used for handling connection handshake response
std::size_t write_length = boost::asio::write( *m_pSocket, boost::asio::buffer(msg.Serialize(), (std::size_t)msg.SerializedLength()));
if (msg.SerializedLength() != write_length)
{
return false;
}
boost::asio::streambuf sbuff;
boost::system::error_code error;
size_t reply_length(0);
// read header for message body length
byte* buffer = rsp.CreateBuffer(MSG_HEADER_LENGTH);
reply_length = boost::asio::read(*m_pSocket, boost::asio::buffer(buffer, MSG_HEADER_LENGTH), boost::asio::transfer_exactly(MSG_HEADER_LENGTH), error);
if (error || !rsp.ReadMessageLength())
{
/* error handled here */
return false;
}
// read message body
int expectedlen = rsp.BodyLength();
buffer = rsp.CreateBuffer(expectedlen);
reply_length = boost::asio::read(*m_pSocket, boost::asio::buffer(buffer, expectedlen), boost::asio::transfer_exactly(expectedlen), error);
if (error)
{
/* error handled here */
return false;
}
if (!rsp.Deserialize() || reply_length != rsp.BodyLength())
{
/* error handled here */
return false;
}
return true;
}
bool Send(CmdPacket& msg)
{
bool bStatus = true;
size_t write_length = 0;
try
{
write_length = boost::asio::write( *m_pSocket, boost::asio::buffer(msg.Serialize(), (std::size_t)msg.SerializedLength()) );
}
catch (...)
{
/* error handled here */
return false;
}
if (write_length != msg.SerializedLength())
{
/* error handled here */
return false;
}
return true;
}
void StartAsyncRead()
{
m_pInMsg = new CmdPacket();
boost::asio::async_read(*m_pSocket, boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH), MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read_header(const boost::system::error_code& error, size_t bytes_transferred)
{
if (!error && bytes_transferred == MSG_HEADER_LENGTH && m_pInMsg->ReadMessageLength())
{
boost::asio::async_read(*m_pSocket,
boost::asio::buffer(m_pInMsg->CreateBuffer(m_pInMsg->SerializedLength()), m_pInMsg->SerializedLength()),
boost::bind(&TcpConnect::handle_read_body, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (error)
{
/* errors handled */
}
}
void handle_read_body(const boost::system::error_code& error, size_t bytes_transferred)
{
bool deserialized = false;
if (!error && (deserialized = m_pInMsg->Deserialize()))
{
// if not yet connected, expecting handshake message, in which case acknowledge, otherwise error
if (m_pInMsg->IsHandshake())
{
m_pInMsg->SetAcknowledge(true);
std::size_t write_length = boost::asio::write(
*m_pSocket, boost::asio::buffer(m_pInMsg->Serialize(), (std::size_t)m_pInMsg->SerializedLength()));
if (write_length == m_pInMsg->SerializedLength())
{
/* we sent the acknowledgement, so we consider we're connected */
}
else
{
/* handling error here */
return;
}
delete m_pInMsg;
m_pInMsg = NULL;
}
// if graceful disconnect, notify the connection manager of new status, which will remove the connection from the map
else if (m_pInMsg->IsDisconnect())
{
/* disconnect request handled here */
return;
}
else
{
/* message received, passing it to the local process here */
}
// set up to handle the next read
m_pInMsg = new CmdPacket;
boost::asio::async_read(*m_pSocket, boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH), MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (error)
{
/* handle case where boost error */
}
else if (!deserialized)
{
/* handle case where message not correctly deserialized */
}
}
protected:
ConnType eConnType;
boost::asio::ip::tcp::socket* m_pSocket{ 0 };
boost::asio::io_service* m_pio_service;
boost::asio::ip::tcp::resolver::iterator m_itEndpoint;
CmdPacket* m_pInMsg{ 0 };
std::string m_strHostIp;
int m_iHostPort;
};
typedef boost::shared_ptr<TcpConnect> ConnectionPtr;
TcpClient class:
class TcpClient : public TcpConnect
{
public:
TcpClient(boost::asio::io_service& io_service, ConnType connType, const std::string& sIP, int iPort)
: TcpConnect(&io_service, connType, sIP, iPort)
{
}
~TcpClient() { /* does what is needed - this works */ }
//virtual ObjType Type() { return OT_CLIENT; }
//virtual int sessionId() { return -1; } // client end does not have a session id
//Use the following to initialize and to to reestablish the connection.
bool Connect()
{
bool bStatus = true;
//Convert the port to a string
std::stringstream ss;
ss << m_iHostPort;
std::string strPort = ss.str();
//Establish the connection
try
{
boost::system::error_code ec;
// create TCP resolver and query and resolve the endpoint
boost::asio::ip::tcp::resolver resolver(*m_pio_service);
boost::asio::ip::tcp::resolver::query query(m_strHostIp.c_str(), strPort.c_str());
boost::asio::ip::tcp::resolver::iterator m_iterEndpoint = resolver.resolve(query, ec);
if (ec)
{
/* error handled here */
bStatus = false;
}
else
{
// close an old socket (shouldn't ever be the case)
if (m_pSocket != NULL) CloseSocket(); /* NOTE: this is defined in TcpConnect, but not shown here */
// create the socket on the io_service object and connect to the endpoint
m_pSocket = new boost::asio::ip::tcp::socket(*m_pio_service);
boost::asio::connect(*m_pSocket, m_iterEndpoint, ec);
if (ec)
{
/* error handled here */
bStatus = false;
}
}
} //end try
catch(...)
{
/* error handled here */
bStatus = false;
}
return bStatus;
}
};
typedef boost::shared_ptr<TcpClient> TcpClientPtr;
TcpServer class (run by TcpWorker and creates TcpSession objects):
class TcpServer;
class TcpSession : public TcpConnect
{
public:
TcpSession(boost::asio::io_service& io_service)
: TcpConnect(&io_service)
, m_session_id(next_session_id())
, m_pSocket(new tcp::socket(io_service))
{
}
virtual ~TcpSession() { /* NOTE: m_pSocket is owned and deleted by TcpConnect */ }
private:
int next_session_id()
{
static int id = 0;
return (++id > 0) ? id : 1;
}
private:
int m_session_id;
};
typedef boost::shared_ptr<TcpSession> TcpSessionPtr;
class TcpServer
{
public:
TcpServer(boost::asio::io_service& io_service, short port)
: m_pio_service(&io_service)
, m_acceptor(io_service, tcp::endpoint(tcp::v4(), port))
{
m_acceptor.listen();
}
~TcpServer()
{
boost::system::error_code errorcode;
m_acceptor.close(errorcode);
}
void start_accept()
{
TcpSessionPtr new_session(new TcpSession(*m_pio_service));
// start listening for this session
m_acceptor.async_accept(new_session->socket(),
boost::bind(&TcpServer::handle_accept, this, new_session,
boost::asio::placeholders::error));
new_session.reset();
}
private:
void handle_accept(TcpSessionPtr new_session, const boost::system::error_code& error)
{
if (!error)
{
new_session->StartAsyncRead(); /* NOTE: there is code for aggregating session objects */
/* NOTE: The result of an async_read() will be handled in TcpConnect::handle_read_header() */
}
else
{
/* error handled here */
}
// listen for the next connection
start_accept();
}
private:
boost::asio::io_service* m_pio_service;
tcp::acceptor m_acceptor;
};
class TcpWorker
{
public:
TcpWorker(unsigned int port)
: m_port(port)
{}
~TcpWorker() {}
void StopWorker()
{
if (!m_io_service.stopped())
{
m_io_service.stop();
while (!m_io_service.stopped()) { boost::this_thread::sleep(boost::posix_time::milliseconds(1)); }
}
}
void operator()() // threaded run function started from Communicator::Listen()
{
TcpServer server(m_io_service, (short)m_port);
server.start_accept(); // set up async_accept() for listening
std::size_t inumhandlers = m_io_service.run(); // blocks here until StopWorker() is called
}
private:
unsigned int m_port;
boost::asio::io_service m_io_service;
bool m_running;
};
Communicator class:
class Communicator {
public:
Communicator() = default;
~Communicator() { /* does what is needed - this works */ }
bool Listen()
{
if (!m_pServerThread || !m_pServerWorker)
{
m_pServerWorker = new TcpWorker(m_myPort);
m_pServerThread = new boost::thread(&TcpWorker::operator(), m_pServerWorker);
return true;
}
return false;
}
bool Connect(int srcId, int destId, std::string ipaddr, int port)
{
bool ret = false;
if (connected(destId))
{
ret = true;
}
else
{
// if io_service is not running, start it (happens if never started, or if no remaining client sockets running)
if (!ClientThreadRunning())
{
if (m_pClientThread) // since going to create a new thread, make sure this one is deleted if exists
delete m_pClientThread;
if (!m_pclient_io_service) // if no io_service has yet been created
m_pclient_io_service = new boost::asio::io_service;
m_pClientServiceThread = new boost::thread(boost::bind(&boost::asio::io_service::run, m_pclient_io_service));
}
// create the connection. Wait for Ack before returning.
TcpClientPtr client(new TcpClient(*m_pclient_io_service, destId, ip, port));
// connect to the client and do a handshake
if (client->Connect())
{
// if an initial handshake works, we're connected
CmdPacket msg(CMD_NONE, srcId, destId, port, ipaddr), rsp;
msg.SetHandshake(); // this starts the handshake protocol, which is completed on receiving the necessary response.
if (!client->SendBlocked(msg, rsp) || rsp != msg)
{
client.reset();
ret = false;
}
else
{
// Connected, now set up for asynchronous reading
client->StartAsyncRead(m_pclient_io_service);
// save it in the class
connection = client;
ret = true;
}
}
// decrement reference count, if not added to shared pointer map, this will set client object for deletion
client.reset();
}
return ret;
}
bool sendMessage(CmdPacket& msg)
{
bool bret = false;
if (connection != nullptr)
{
iter->second->Send(msg);
}
return bret;
}
private:
TcpConnect *connection{ 0 };
TcpWorker* m_pServerWorker{ 0 };
boost::thread *m_pServerThread{ 0 };
boost::thread* m_pClientThread{ 0 };
boost::asio::io_service* m_pclient_io_service{ 0 };
ConnectionPtr connection{ 0 };
};
Your code is not self-contained. It's also out of synch (TcpConnect constructors do not match the initializer lists, AsyncStartRead is called with an argument). It looks like you might have duplicated m_pSocket members in the TcpSession and TcpConnect classes, namely:
is very contradictory.
There's a unhealthy reliance on pointers obscuring ownership and lifetime.
If you actually enable compiler warnings, the compiler will let you know that several member variables are is not initialized in the order you seem to expect (based on the ordering in the constructor's initializer lists).
There is the code smell of race conditions (sleep in a loop with a boolean check?). Unused variabled (m_running?).
Undeclared variables with very confused names (what is m_pClientServiceThread? What makes it different from m_pServerThread and m_pClientThread?
Why is connection declared twice, once as a raw pointer, once as a shared_ptr?
Many functions are left out (connected? ClientThreadRunning?), variables (mentioned above, ip, iter comes out of nowhere), etc.
All in all, I stopped trying to make the sample compile after an hour or two:
Broken on Coliru
#undef NDEBUG // make sure we assert
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <boost/thread.hpp>
namespace json = boost::json;
using byte = char;
using boost::asio::ip::tcp;
enum class ConnType {
Session
};
using Big32 = boost::endian::big_uint32_t;
static constexpr size_t MSG_HEADER_LENGTH = 2 * sizeof(Big32);
enum CmdType {
CMD_NONE,
CMD_HANDSHAKE,
CMD_ACK,
CMD_DISCONNECT,
};
struct CmdPacket {
std::string _buf;
json::value _data; // { "cmd_type": MsgType, "value": json::value }
bool IsHandshake() const { return _data.at("cmd_type") == CMD_HANDSHAKE; }
bool IsDisconnect() const{ return _data.at("cmd_type") == CMD_DISCONNECT; }
void SetAcknowledge(json::value v) {
auto& o = _data.as_object();
o["cmd_type"] = CMD_ACK;
o["value"] = v;
}
void SetHandshake() { _data.as_object()["cmd_type"] = CMD_HANDSHAKE; }
byte* CreateBuffer(uint32_t n) { _buf.assign(n, '\0'); return _buf.data(); }
uint32_t ReadMessageLength() const {
assert(_buf.size() >= sizeof(Big32));
boost::endian::big_uint32_t decode;
memcpy(&decode, _buf.data(), sizeof(decode));
return decode;
}
uint32_t BodyLength() const {
return ReadMessageLength();
}
std::string Serialize() const { return json::serialize(_data); }
bool Deserialize() {
json::error_code ec;
_data = json::parse(_buf, ec);
return !ec;
}
size_t SerializedLength() const { return Serialize().length(); }
};
class TcpConnect : public boost::enable_shared_from_this<TcpConnect> {
public:
virtual ~TcpConnect()
{ /* does what is needed - this works */
}
auto& socket() { assert(m_pSocket); return *m_pSocket; }
auto& socket() const { assert(m_pSocket); return *m_pSocket; }
protected:
TcpConnect(boost::asio::io_service* pio_service, ConnType connType,
std::string sHostIp, uint16_t iHostPort)
: m_pio_service(pio_service)
, eConnType(connType)
, m_pSocket(new tcp::socket(*pio_service))
, m_strHostIp(sHostIp)
, m_iHostPort(iHostPort)
{
}
void CloseSocket() {
if (m_pSocket)
m_pSocket->close();
}
bool SendBlocked(CmdPacket& msg, CmdPacket& rsp)
{
// Function used for handling connection handshake response
std::size_t write_length = boost::asio::write(
*m_pSocket,
boost::asio::buffer(msg.Serialize(),
(std::size_t)msg.SerializedLength()));
if (msg.SerializedLength() != write_length) {
return false;
}
boost::asio::streambuf sbuff;
boost::system::error_code error;
size_t reply_length(0);
// read header for message body length
byte* buffer = rsp.CreateBuffer(MSG_HEADER_LENGTH);
reply_length = boost::asio::read(
*m_pSocket, boost::asio::buffer(buffer, MSG_HEADER_LENGTH),
boost::asio::transfer_exactly(MSG_HEADER_LENGTH), error);
if (error || !rsp.ReadMessageLength()) {
/* error handled here */
return false;
}
// read message body
int expectedlen = rsp.BodyLength();
buffer = rsp.CreateBuffer(expectedlen);
reply_length = boost::asio::read(
*m_pSocket, boost::asio::buffer(buffer, expectedlen),
boost::asio::transfer_exactly(expectedlen), error);
if (error) {
/* error handled here */
return false;
}
if (reply_length != rsp.BodyLength() || !rsp.Deserialize()) {
/* error handled here */
return false;
}
return true;
}
bool Send(CmdPacket& msg)
{
//bool bStatus = true;
size_t write_length = 0;
try {
write_length = boost::asio::write(
*m_pSocket,
boost::asio::buffer(msg.Serialize(),
(std::size_t)msg.SerializedLength()));
} catch (...) {
/* error handled here */
return false;
}
if (write_length != msg.SerializedLength()) {
/* error handled here */
return false;
}
return true;
}
public:
void StartAsyncRead()
{
m_pInMsg = new CmdPacket();
boost::asio::async_read(
*m_pSocket,
boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH),
MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read_header(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error && bytes_transferred == MSG_HEADER_LENGTH &&
m_pInMsg->ReadMessageLength()) {
boost::asio::async_read(
*m_pSocket,
boost::asio::buffer(
m_pInMsg->CreateBuffer(m_pInMsg->SerializedLength()),
m_pInMsg->SerializedLength()),
boost::bind(&TcpConnect::handle_read_body, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else if (error) {
/* errors handled */
}
}
void handle_read_body(const boost::system::error_code& error,
size_t bytes_transferred)
{
bool deserialized = false;
if (!error && (deserialized = m_pInMsg->Deserialize())) {
// if not yet connected, expecting handshake message, in which case
// acknowledge, otherwise error
if (m_pInMsg->IsHandshake()) {
m_pInMsg->SetAcknowledge(true);
std::size_t write_length = boost::asio::write(
*m_pSocket,
boost::asio::buffer(
m_pInMsg->Serialize(),
(std::size_t)m_pInMsg->SerializedLength()));
if (write_length == m_pInMsg->SerializedLength()) {
/* we sent the acknowledgement, so we consider we're
* connected */
} else {
/* handling error here */
return;
}
delete m_pInMsg;
m_pInMsg = NULL;
}
// if graceful disconnect, notify the connection manager of new
// status, which will remove the connection from the map
else if (m_pInMsg->IsDisconnect()) {
/* disconnect request handled here */
return;
} else {
/* message received, passing it to the local process here */
}
// set up to handle the next read
m_pInMsg = new CmdPacket;
boost::asio::async_read(
*m_pSocket,
boost::asio::buffer(m_pInMsg->CreateBuffer(MSG_HEADER_LENGTH),
MSG_HEADER_LENGTH),
boost::bind(&TcpConnect::handle_read_header, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else if (error) {
/* handle case where boost error */
} else if (!deserialized) {
/* handle case where message not correctly deserialized */
}
}
protected:
boost::asio::io_service* m_pio_service;
ConnType eConnType;
boost::asio::ip::tcp::socket* m_pSocket{0};
boost::asio::ip::tcp::resolver::iterator m_itEndpoint;
CmdPacket* m_pInMsg{0};
std::string m_strHostIp;
uint16_t m_iHostPort;
};
typedef boost::shared_ptr<TcpConnect> ConnectionPtr;
// TcpClient class:
class TcpClient : public TcpConnect {
public:
TcpClient(boost::asio::io_service& io_service, ConnType connType,
const std::string& sIP, uint16_t iPort)
: TcpConnect(&io_service, connType, sIP, iPort)
{
}
~TcpClient()
{ /* does what is needed - this works */
}
// virtual ObjType Type() { return OT_CLIENT; }
// virtual int sessionId() { return -1; } // client end does not have a
// session id
// Use the following to initialize and to to reestablish the connection.
bool Connect()
{
bool bStatus = true;
// Convert the port to a string
std::stringstream ss;
ss << m_iHostPort;
std::string strPort = ss.str();
// Establish the connection
try {
boost::system::error_code ec;
// create TCP resolver and query and resolve the endpoint
boost::asio::ip::tcp::resolver resolver(*m_pio_service);
boost::asio::ip::tcp::resolver::query query(m_strHostIp.c_str(),
strPort.c_str());
boost::asio::ip::tcp::resolver::iterator m_iterEndpoint =
resolver.resolve(query, ec);
if (ec) {
/* error handled here */
bStatus = false;
} else {
// close an old socket (shouldn't ever be the case)
if (m_pSocket != NULL)
CloseSocket(); /* NOTE: this is defined in TcpConnect, but
not shown here */
// create the socket on the io_service object and connect to the
// endpoint
m_pSocket = new boost::asio::ip::tcp::socket(*m_pio_service);
boost::asio::connect(*m_pSocket, m_iterEndpoint, ec);
if (ec) {
/* error handled here */
bStatus = false;
}
}
} // end try
catch (...) {
/* error handled here */
bStatus = false;
}
return bStatus;
}
};
typedef boost::shared_ptr<TcpClient> TcpClientPtr;
// TcpServer class (run by TcpWorker and creates TcpSession objects):
class TcpServer;
class TcpSession : public TcpConnect {
public:
TcpSession(boost::asio::io_service& io_service)
: TcpConnect(&io_service, ConnType::Session, "127.0.0.1", 8787)
, m_session_id(next_session_id())
{
}
virtual ~TcpSession()
{ /* NOTE: m_pSocket is owned and deleted by TcpConnect */
}
private:
int next_session_id()
{
static int id = 0;
return (++id > 0) ? id : 1;
}
private:
int m_session_id;
};
typedef boost::shared_ptr<TcpSession> TcpSessionPtr;
class TcpServer {
public:
TcpServer(boost::asio::io_service& io_service, uint16_t port)
: m_pio_service(&io_service)
, m_acceptor(io_service, tcp::endpoint(tcp::v4(), port))
{
m_acceptor.listen();
}
~TcpServer()
{
boost::system::error_code errorcode;
m_acceptor.close(errorcode);
}
void start_accept()
{
TcpSessionPtr new_session(new TcpSession(*m_pio_service));
// start listening for this session
m_acceptor.async_accept(new_session->socket(),
boost::bind(&TcpServer::handle_accept, this,
new_session,
boost::asio::placeholders::error));
new_session.reset();
}
private:
void handle_accept(TcpSessionPtr new_session,
const boost::system::error_code& error)
{
if (!error) {
new_session->StartAsyncRead(); /* NOTE: there is code for
aggregating session objects */
/* NOTE: The result of an async_read() will be handled in
* TcpConnect::handle_read_header() */
} else {
/* error handled here */
}
// listen for the next connection
start_accept();
}
private:
boost::asio::io_service* m_pio_service;
tcp::acceptor m_acceptor;
};
class TcpWorker {
public:
TcpWorker(uint16_t port) : m_port(port) {}
~TcpWorker() {}
void StopWorker()
{
if (!m_io_service.stopped()) {
m_io_service.stop();
while (!m_io_service.stopped()) {
boost::this_thread::sleep(boost::posix_time::milliseconds(1));
}
}
}
void
operator()() // threaded run function started from Communicator::Listen()
{
TcpServer server(m_io_service, m_port);
server.start_accept(); // set up async_accept() for listening
std::size_t inumhandlers =
m_io_service.run(); // blocks here until StopWorker() is called
}
private:
uint16_t m_port;
boost::asio::io_service m_io_service;
bool m_running;
};
// Communicator class:
class Communicator {
public:
uint16_t m_myPort = 8787;
Communicator() = default;
~Communicator()
{ /* does what is needed - this works */
}
bool Listen()
{
if (!m_pServerThread || !m_pServerWorker) {
m_pServerWorker = new TcpWorker(m_myPort);
m_pServerThread =
new boost::thread(&TcpWorker::operator(), m_pServerWorker);
return true;
}
return false;
}
bool Connect(int srcId, int destId, std::string ipaddr, uint16_t port)
{
bool ret = false;
if (connected(destId)) {
ret = true;
} else {
// if io_service is not running, start it (happens if never started,
// or if no remaining client sockets running)
if (!ClientThreadRunning()) {
if (m_pClientThread) // since going to create a new thread, make
// sure this one is deleted if exists
delete m_pClientThread;
if (!m_pclient_io_service) // if no io_service has yet been
// created
m_pclient_io_service = new boost::asio::io_service;
m_pClientServiceThread = new boost::thread(boost::bind(
&boost::asio::io_service::run, m_pclient_io_service));
}
// create the connection. Wait for Ack before returning.
TcpClientPtr client(
new TcpClient(*m_pclient_io_service, destId, ip, port));
// connect to the client and do a handshake
if (client->Connect()) {
// if an initial handshake works, we're connected
CmdPacket msg(CMD_NONE, srcId, destId, port, ipaddr), rsp;
msg.SetHandshake(); // this starts the handshake protocol, which
// is completed on receiving the necessary
// response.
if (!client->SendBlocked(msg, rsp) || rsp != msg) {
client.reset();
ret = false;
} else {
// Connected, now set up for asynchronous reading
client->StartAsyncRead(m_pclient_io_service);
// save it in the class
connection = client;
ret = true;
}
}
// decrement reference count, if not added to shared pointer map,
// this will delete client object
client.reset();
}
return ret;
}
bool sendMessage(CmdPacket& msg)
{
bool bret = false;
if (connection != nullptr) {
iter->second->Send(msg);
}
return bret;
}
private:
ConnectionPtr connection;
TcpWorker* m_pServerWorker{0};
boost::thread* m_pServerThread{0};
boost::thread* m_pClientThread{0};
boost::asio::io_service* m_pclient_io_service{0};
};
int main() {}
Out Of The Box
All of this reminds me very much of this answer: How do I make this HTTPS connection persistent in Beast? in that answer I show how to to keep a pool of connections for different destinations. It uses a different style, so hopefully you can use that as inspiration.

boost.asio async_write timeout not working when the server is down ungracefully

I refer to the example from boost example:async_tcp_client.cpp. This example shows how to set timeout for aync_read. So I write a test program to test async_write timeout according to this example.
The write code is:
bool AsioAsyncTCPClientImpl::send(const uint8_t* data, int32_t length)
{
if (!m_isConnected || length <= 0) return false;
m_deadline.expires_after(std::chrono::seconds(5));
boost::system::error_code ec;
boost::asio::async_write(m_client, boost::asio::buffer(data, length),
std::bind(&AsioAsyncTCPClientImpl::handleWrite, this, std::placeholders::_1, std::placeholders::_2));
return true;
}
the handleWrite code is:
void AsioAsyncTCPClientImpl::handleWrite(const boost::system::error_code& ec, std::size_t len)
{
if (!m_started) return;
if (ec == boost::asio::error::eof)
{
fireConnectionCallback(ConnectionState::Disconnected);
return;
}
if (ec)
{
fireErrorCallback(ec);
return;
}
m_deadline.expires_at(boost::asio::steady_timer::time_point::max());
}
From my test, if I disable the network or pull out the cable of the PC where the server is running, the async_write will always completed as normal, so the timeout set is not working. I am wondering if I miss something, hope some one familiar with this could give me some clue, Thanks for advance!
Update
The async_wait code:
bool AsioAsyncTCPClientImpl::start()
{
if (m_started) return true;
connect();
m_deadline.async_wait(std::bind(&AsioAsyncTCPClientImpl::checkTimeout, this));
m_started = true;
m_ioLoopThread = std::thread(&AsioAsyncTCPClientImpl::loopProcess, this);
return true;
}
void AsioAsyncTCPClientImpl::checkTimeout()
{
if (!m_started) return;
if (m_deadline.expiry() <= boost::asio::steady_timer::clock_type::now())
{
std::cout << "wait timeout" << std::endl;
disconnect();
m_deadline.expires_at(boost::asio::steady_timer::time_point::max());
}
m_deadline.async_wait(std::bind(&AsioAsyncTCPClientImpl::checkTimeout, this));
}
And I put the run method of io_context in a thread, I am not sure if this is right way to do it, because I don't want to run io_context.run() in main function.
void AsioAsyncTCPClientImpl::loopProcess()
{
while(m_started)
{
m_context.run();
}
std::cout << "loop process exited" << std::endl;
}
You never await the timer.
Something like
m_deadline.async_wait(
std::bind(&AsioAsyncTCPClientImpl::handleTimer, this, boost::asio::placeholders::errpr));
And then
void AsioAsyncTCPClientImpl::handleTimer(boost::system::error_code ec) {
if (!ec) {
std::cout << "Timeout expired" << std::endl;
m_client.cancel();
}
}

Boost UDP async_receive_from doesn't wait for timeout or receive

I try to use the async_receive_from method to get data. But when i start, the program doesn't wait for the timeout or is reading something. I know that data is incoming on the port (wireshark). What could be the reason for that. I tried to use different examples from the boost website. When I'm debugging the Code, I always get the errorcode 125 (operation_aborted).
Here is the relevant part of my code:
UDPCommunication::UDPCommunication(){
m_portReceive = 2041;
m_byteArrayLen = 0;
m_lengthReceive = 0;
m_dataReceived = false;
}
void UDPCommunication::openSocketReceive(){
try{
m_socketReceive = shared_ptr<udp::socket>(
new udp::socket(m_ioService,
udp::endpoint(udp::v4(), m_portReceive)));
m_receiveTimeout = shared_ptr<boost::asio::deadline_timer>(
new boost::asio::deadline_timer(m_ioService));
m_receiveTimeout->async_wait(
boost::bind(&UDPCommunication::udpReceiveTimeoutHandler, this,
boost::asio::placeholders::error, 0));
m_dataReceived = false;
} catch(std::exception& e){
std::cerr << e.what() << std::endl;
ErrorLogging::log(e);
}
}
void UDPCommunication::udpReceiveWithTimeout(long time){
try{
boost::system::error_code ec;
m_receiveTimeout->expires_from_now(boost::posix_time::seconds(time));
ec = boost::asio::error::would_block;
m_lengthReceive = 0;
m_socketReceive->async_receive_from(buffer(m_bufferReceive),
m_endpointReceive,
boost::bind(&UDPCommunication::udpReceiveTimeoutHandler, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
do{
m_ioService.run_one();
}while(ec == boost::asio::error::would_block);
} catch(std::exception& e){
std::cerr << e.what() << std::endl;
ErrorLogging::log(e);
}
}
bool UDPCommunication::udpReceiveTimeoutHandler(
const boost::system::error_code& ec, size_t size){
try{
m_socketReceive->cancel();
m_receiveTimeout->cancel();
if(ec != boost::asio::error::would_block){
m_dataReceived = false;
return false;
}else{
m_lengthReceive = sizeof(m_bufferReceive);
m_vectorBuffer.resize(m_lengthReceive);
int i = 0;
for(std::vector<unsigned char>::iterator it =
m_vectorBuffer.begin(); it != m_vectorBuffer.end(); ++it){
*it = m_bufferReceive[i];
++i;
}
m_dataReceived = true;
return true;
}
} catch(std::exception& e){
std::cerr << e.what() << std::endl;
ErrorLogging::log(e);
return false;
}
return false;
}
bool UDPCommunication::dataReceived(){
return m_dataReceived;
}
void main(){
udpCommunication = shared_ptr<UDPCommunication>(new UDPCommunication());
udpCommunication->openSocketReceive();
udpCommunication->udpReceiveWithTimeout(5);
bool dataReceived = udpCommunication->dataReceived();
std::cout<< dataReceived << std::endl; //is data coming?
}
Solution of the upper probblem.
This Code works fine:
int m_portReceive;
int m_byteArrayLen;
io_service m_ioService;
udp::endpoint m_endpointReceive;
shared_ptr<udp::socket> m_socketReceive;
shared_ptr<boost::asio::deadline_timer> m_receiveTimeout;
std::vector<unsigned char> m_vectorBuffer;
unsigned char m_bufferReceive[128];
size_t m_lengthReceive;
bool m_dataReceived;
bool m_timeoutFired;
boost::mutex m_mutex;
UDPCommunication::UDPCommunication(){
m_portReceive = 2041;
m_byteArrayLen = 0;
m_lengthReceive = 0;
m_dataReceived = false;
m_timeoutFired = false;
}
void UDPCommunication::openSocketReceive(){
try{
m_socketReceive = shared_ptr<udp::socket>(
new udp::socket(m_ioService,
udp::endpoint(udp::v4(), m_portReceive)));
m_receiveTimeout = shared_ptr<boost::asio::deadline_timer>(
new boost::asio::deadline_timer(m_ioService));
m_receiveTimeout->expires_at(boost::posix_time::pos_infin);
UDPCommunication::udpTimeoutHandler();
m_receiveTimeout->async_wait(
boost::bind(&UDPCommunication::udpTimeoutHandler, this));
m_dataReceived = false;
} catch(std::exception& e){
std::cerr << e.what() << std::endl;
ErrorLogging::log(e);
}
}
void UDPCommunication::udpReceiveWithTimeout(long time){
try{
boost::system::error_code ec;
m_receiveTimeout->expires_from_now(
boost::posix_time::time_duration(
boost::posix_time::seconds(time)));
m_socketReceive->async_receive_from(buffer(m_bufferReceive),
m_endpointReceive,
boost::bind(&UDPCommunication::udpReceiveHandler, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
do{
m_ioService.run_one();
}while((m_lengthReceive == 0)
&& (m_timeoutFired == false));
} catch(std::exception& e){
std::cerr << e.what() << std::endl;
ErrorLogging::log(e);
}
}
void UDPCommunication::udpReceiveHandler(const boost::system::error_code& ec,
size_t size){
try{
m_mutex.lock();
if(m_timeoutFired == false){
if(size > 0){
m_socketReceive->cancel();
m_receiveTimeout->cancel();
m_lengthReceive = size;
m_vectorBuffer.resize(m_lengthReceive);
int i = 0;
for(std::vector<unsigned char>::iterator it =
m_vectorBuffer.begin(); it != m_vectorBuffer.end();
++it){
*it = m_bufferReceive[i];
++i;
}
m_dataReceived = true;
}else{
m_lengthReceive = 0;
m_socketReceive->async_receive_from(buffer(m_bufferReceive),
m_endpointReceive,
boost::bind(&UDPCommunication::udpReceiveHandler, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
m_mutex.unlock();
} catch(std::exception& e){
std::cerr << e.what() << std::endl;
ErrorLogging::log(e);
m_mutex.unlock();
}
}
void UDPCommunication::udpTimeoutHandler(){
try{
m_mutex.lock();
if(m_lengthReceive == 0){
if(m_receiveTimeout->expires_at()
<= deadline_timer::traits_type::now()){
std::cout << "timeout: no data received from modem"
<< std::endl;
m_socketReceive->cancel();
m_dataReceived = false;
m_receiveTimeout->expires_at(boost::posix_time::pos_infin);
m_timeoutFired = true;
}
m_receiveTimeout->async_wait(
boost::bind(&UDPCommunication::udpTimeoutHandler, this));
}
m_mutex.unlock();
} catch(std::exception& e){
std::cerr << e.what() << std::endl;
ErrorLogging::log(e);
m_mutex.unlock();
}
}
bool UDPCommunication::dataReceived(){
return m_dataReceived;
}
void main(){
udpCommunication = shared_ptr<UDPCommunication>(new UDPCommunication());
udpCommunication->openSocketReceive();
udpCommunication->udpReceiveWithTimeout(5);
bool dataReceived = udpCommunication->dataReceived();
std::cout<< dataReceived << std::endl; //is data coming?
//now do something with data...
}
Note that i have not checked every line for other errors but two issues spring to mind:
You async_wait on your deadline_timer without setting an expiry time first.
You use io_service.run_one() without calling io_service.reset() in between.
I am not sure what the first does but the second means, that once the run_one() has returned for the first time (either by posting any handler or by beeing stopped) it will immediatly return every time it is called again, not doing its job.
Possible that the first one calls your timeout handler with operation_aborted and the second point keeps your io_service from ever doing anything else.
edit:
There is also a problem with
do{
m_ioService.run_one();
}while(ec == boost::asio::error::would_block);
If you want the run_one() method to alter ec, you have to pass it like so: run_one(ec)
As is, the ec is a local variable in that method and should not get modified by anything thus always remain would_block. Of course, in that case run_one would no longer throw anything but store its result in ec.

Boost Async Server Class : Debug Assertion Failed and Empty Buffer

Can not find why this program is failing. It must be my boost usage. Problem is highlighted in comment and there is a small note about some of the function calls
/* Includes Hidden */
using boost::asio::ip::udp;
class UDP_Server {
public:
UDP_Server(boost::asio::io_service& IO, unsigned short PORT)
: sock(IO, udp::endpoint(udp::v4(),PORT)) {
Listen();
}
~UDP_Server() {
for(auto& endpoint : Clients) {
delete endpoint;
}
Clients.clear();
}
void Listen() {
//waits for msg to be sent. Captures end point and sends address
//so server can store connections
udp::endpoint* T = new udp::endpoint;
sock.async_receive_from(
boost::asio::buffer(rbuf),*T,
boost::bind(&UDP_Server::handle_rec, this, T,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_rec(udp::endpoint* EP, const boost::system::error_code& err, size_t len) {
//When the program enters here, err is 234 (windows error for more data available)
//len is 0 and rbuf is empty.
if(err && err != boost::asio::error::message_size) {
std::cerr << err.message() << std::endl;
}
std::cout.write(rbuf.data(),rbuf.size());
bool ThisClient = false;
std::string Msg = "";
for( auto& EPs : Clients) {
if(EPs == EP) {
ThisClient = true; break;
}
}
if(!ThisClient) {
if(len > 0 && rbuf[0]=='0') {
Clients.push_back(EP);
Msg = "Connected";
}else{
Msg = "Connection Refused";
}
}else{
if(rbuf[0]=='0') {
delete EP;
Clients.remove(EP);
Msg = "Disconnected";
}
}
//queue message to send back and call handle_snd function
sock.async_send_to(boost::asio::buffer(Msg),*EP,
boost::bind(&UDP_Server::handle_snd,this,EP,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
));
Listen(); //listen for some more messages!
} //debugging through the first time through this function eventually exits here
//and ends up going through a bunch of code I didn't write, and ultimately fail.
void handle_snd(udp::endpoint *Dest, const boost::system::error_code& err, size_t len) {
}
private:
udp::socket sock;
std::list<udp::endpoint*> Clients;
std::vector<char> rbuf;
};
void HostStart() {
try {
boost::asio::io_service io;
UDP_Server Host(io,13);
io.run();
}catch(std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
int main() {
std::thread thd(HostStart); //start server
try {
boost::asio::io_service io2;
udp::resolver res(io2);
udp::resolver::query queer(udp::v4(),"127.0.0.1","daytime");
udp::endpoint HostEP = *res.resolve(queer);
udp::socket sock(io2);
sock.open(udp::v4());
std::string Msg = "0";
std::vector<char> MsgArray(Msg.begin(),Msg.end());
sock.send_to(boost::asio::buffer(Msg),HostEP);
io2.run();
udp::endpoint RecEP;
std::array<char,128> rbuf;
sock.receive_from(boost::asio::buffer(rbuf),RecEP);
std::cout.write(rbuf.data(),rbuf.size());
sock.send_to(boost::asio::buffer(Msg),HostEP);
sock.receive_from(boost::asio::buffer(rbuf),RecEP);
std::cout.write(rbuf.data(),rbuf.size());
}catch(std::exception& e) {
std::cerr << e.what() << std::endl;
}
Sleep(10000);
return 0;
}
If I use debugging and walk through this code, I find that I ultimately end up in a file called
win_iocp_io_service.ipp
and I get this error:
In my main, I'm just trying to synch send a couple message to test the asynch server class. I have no idea why the buffer is empty after the async server call and why I am getting this error.
Possibly it is related to when I call run on my io service and the way I am trying to multithread it.
Thank you
This may be the result of the program invoking undefined behavior. Within UDP_Server::handle_rec(), the call to udp::socket::async_send_to() violates the requirement that the underlying memory provided to the buffer must remain valid until the handler is called.
Although the buffers object may be copied as necessary, ownership of the underlying memory blocks is retained by the caller, which must guarantee that they remain valid until the handler is called.
To meet this criteria, consider making Msg a data member of UDP_Server, rather than an automatic variable.
Also, two other points to consider:
UDP_Server::rbuf will always maintain a size of zero, causing udp::socket::async_receive_from() in UDP_Server::Listen() to read nothing, as there is no memory into which data can be read. udp::socket::async_receive_from() only modifies the contents of the memory block provided to it as a buffer; it is the caller's responsibility to have already allocated the memory block. Either resize rbuf to a size large enough to handle all incoming datagrams, or lazily allocate the buffer.
In main(), rbuf.size() will always return 128, regardless of how much data was actually received. udp::socket::receive_from()'s return value indicates the number of bytes received, and this value should be used when creating a boost::asio::buffer and when writing to std::cout.