boost:asio::async_write: Data sent but handler not called - c++

I have the following class definition:
// SocketTypeT may be e.g. 'boost::asio::ip::tcp::socket'
template<class SocketTypeT>
class Socket : public SocketTypeT, public boost::enable_shared_from_this< Socket<SocketTypeT> > {
[...]
Within this class I have the following method 'writeAsync':
void writeAsync(const std::string& strData) {
boost::asio::async_write(*this, boost::asio::buffer(strData),
boost::bind(&Socket::handle_write_async,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
And finally the handler (also a class member function) used in 'writeAsync':
void handle_write_async(const boost::system::error_code& ec, std::size_t cntBytesSent) {
cout << "handle_write_async" << endl;
if (m_pSocketAsyncObserver) {
m_pSocketAsyncObserver->handleAsyncWrite(connectionClosed, cntBytesSent, ec);
}
}
Problem:
The data is successfully transmitted to the server, however 'handle_write_async' gets never called. What might be the reason for this?

For continuous execution of run you need to supply io_service::work object. Please read this question

Related

Simple Boost TCP Server, example from the book "C++ Crash Course"

I'm trying to understand the thing with std::enable_shared_from_this in case of TCP connections and I see it like when first connection is accepted in the serve func, object of the class Session is created and later invocations just create shared_ptr to the same object isn't it? If I get it well, I'm not sure is it completely correct to move everytime socket in serve? The below example is like original one from the book besides connections int I've added:
using namespace boost::asio;
int connections{};
struct Session : std::enable_shared_from_this<Session> {
explicit Session(ip::tcp::socket socket) : socket{ std::move(socket) } {}
void read() {
async_read_until(socket, dynamic_buffer(message), '\n',
[self=shared_from_this()] (boost::system::error_code ec,
std::size_t length) {
if(ec || self->message == "\n") {
std::cout<<"Ended connection as endline was sent\n" ;
return;
}
boost::algorithm::to_upper(self->message);
self->write();
});
}
void write() {
async_write(socket, buffer(message),
[self=shared_from_this()] (boost::system::error_code ec,
std::size_t length) {
if(ec) return;
self->message.clear();
self->read();
});
}
private:
ip::tcp::socket socket;
std::string message;
};
void serve(ip::tcp::acceptor& acceptor) {
acceptor.async_accept([&acceptor](boost::system::error_code ec,
ip::tcp::socket socket) {
serve(acceptor);
if (ec) return;
auto session = std::make_shared<Session>(std::move(socket));
std::cout<<"Connection established no "<<++connections<<"\n";
session->read();
});
}
int main(){
try{
io_context io_context;
ip::tcp::acceptor acceptor{ io_context,
ip::tcp::endpoint(ip::tcp::v4(), 1895)};
serve(acceptor);
io_context.run();
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
Socket is moved at each invocation of serve because it is a fresh socket for a newly established connection. Note that it is passed by value and unless moved to some long-living object (session in this case) it will be immediately destroyed after going out of scope, ending the connection.
"object of the class Session is created and later invocations just create shared_ptr to the same object isn't it" - nope, each make_shared invocation creates a new session object - one per connection. shared_from_this spawns pointer to the current object.

How to make async_write_some() data argument in the buffer dynamic with C++ boost::asio?

I've been making a simple server using boost::asio. I've used Boost's docs to create basics and ran into a problem:
I want a client to be able to send the server a command, so I want the server to be able to respond to that. I use async_write_some() and async_read_some(), as shown in the docs.
I use m_Data member to store whatever a client sent to the server, but I don't know how to make that async_write_some() send a certain thing.
Connection.hpp
using namespace boost::asio;
class Connection : public boost::enable_shared_from_this<Connection> {
private:
ip::tcp::socket m_Socket;
std::string m_Msg;
uint16_t m_MaxLen;
char* m_Data;
public:
Connection(boost::asio::io_service& _ioSrvc);
static boost::shared_ptr<Connection> Create(boost::asio::io_service& _ioSrvc);
ip::tcp::socket& Socket();
void Start();
void HandleRead(const boost::system::error_code& _err, size_t _bytes);
void HandleWrite(const boost::system::error_code& _err, size_t _bytes);
};
Connection.cpp
Connection::Connection(boost::asio::io_service& _ioSrvc)
: m_Socket(_ioSrvc), m_Msg("placeholder_message"), m_MaxLen(1024), m_Data(new char[m_MaxLen]) { }
boost::shared_ptr<Connection> Connection::Create(boost::asio::io_service& _ioSrvc) {
return boost::shared_ptr<Connection>(new Connection(_ioSrvc));
}
ip::tcp::socket& Connection::Socket() {
return m_Socket;
}
void Connection::Start() {
m_Socket.async_read_some(boost::asio::buffer(m_Data, m_MaxLen),
boost::bind(&Connection::HandleRead, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
m_Socket.async_write_some(boost::asio::buffer(m_Msg, m_MaxLen),
boost::bind(&Connection::HandleWrite, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void Connection::HandleRead(const boost::system::error_code& _err, size_t _bytes) {
if (!_err) {
std::cout << m_Data << std::endl;
}
else {
std::cerr << "[ERROR]: " << _err.message() << std::endl;
m_Socket.close();
}
}
void Connection::HandleWrite(const boost::system::error_code& _err, size_t _bytes) {
if (!_err) {
std::cout << "!" << std::endl;
}
else {
std::cerr << "[ERROR]: " << _err.message() << std::endl;
m_Socket.close();
}
}
I know it takes a buffer, which takes a reference to a string, so I thought that changing the original string, which is m_Msg member, would result in server being able to dynamically change the response to whatever I wanted, but it didn't work, it looked like the buffer took a copy instead of a reference, though I'm sure I saw that std::string& arg in it. I tried to change the m_Msg member in HandleRead() method, using outside method and many stuff but nothing worked for me, the client would always end up receiving "placeholder_message", which m_Msg is set to in the constructor.
So basically, I want to send various data based on the received data and I don't know how to do that and I'm asking for help.
Thank you in advance!

How can lambda functions and function typedefinitions work as a listener?

I'm trying to make the equivalent of a Event Listener from Java, but in C++.
My goal is, that I can call a function from a class, which triggers my listener I added to this class.
I found the following Link which gave me a solution to do this.
The problem hereby is, that my program crashed as soon as I tried to call the listeners.
My code is structured like this:
class MessageHandler abstract
{
public:
typedef const std::function<void(int, std::string)> Handler;
void addHandler(Handler& handler) {
handlers.push_back(&handler);
}
private:
std::vector<Handler*> handlers;
protected:
void someFunction(int id, std::string message) {
for (auto& handler : handlers) {
(*handler)(id, message); //Here it will crash
}
}
};
As you maybe already mentioned, this is the base class from which I derive some childclasses. These childclasses call then my "someFunction" code.
And the class where I create one of these childclasses, is structured like this:
class Server
{
private:
SubHandler handler;
void setHandlers() {
handler.addHandler([&](int id, std::string message) { executingFunction(id, message); });
}
void executingFunction(int id, std::string message) {
std::cout << "Listener Worked!" << std::endl;
//Not actually the code inside, but it doesn't matter, case I don't even get to this code
}
};
The program crashes at the line, where I loop over my listeners and call them with error:
"Access violation when reading at position 0x000000000000000010."
(This is translated, so its not the message you will get if you have your Visual Studio set to English)
You should compile your code using /permissive-. The compiler should refuse your code.
void addHandler(Handler& handler) {
handlers.push_back(&handler);
}
You shouldn't be able to send a temporary to this function, but yet you are!
// v----- This lambda is a temporary object --------------------------v
handler.addHandler([&](int id, std::string message) { executingFunction(id, message); });
The lambda object created at that line dies just after the statement is finished.
// v---- pointer to the temporary.
handlers.push_back(&handler);
My recomendation would be to drop the pointer and use std::function object by value. They are made to be used like that:
// abstract is not a C++ keyword.
class MessageHandler /* abstract */
{
public:
// using instead of typedef and non const
using Handler = std::function<void(int, std::string)>;
void addHandler(Handler const& handler) { // const reference
// insert by value
handlers.push_back(handler);
}
private:
// no pointer here.
std::vector<Handler> handlers;
protected:
void someFunction(int id, std::string message) {
for (auto const& handler : handlers) {
handler(id, message); //Here it will not crash anymore
}
}
};
This is because your lambda defined in your Server class method isn't in the scope of your MessageHandler class. I suggest you read through this : https://blog.feabhas.com/2014/03/demystifying-c-lambdas/ to get a good idea of what the problem is and how to fix it.
Though, it might be a good solution to define a struct holding your lambda, which would then work with std::mem_fn.
Hope this helps
Your source is bad :/
You might use instead something like:
class MessageHandler
{
public:
using Handler = std::function<void(int, const std::string&)> Handler;
void addHandler(const Handler& handler) { handlers.push_back(handler); }
void execute(int id, const std::string& message) {
for (auto& handler : handlers) {
(*handler)(id, message);
}
}
private:
std::vector<Handler> handlers;
};
And then use it:
class Server
{
private:
MessageHandler handler;
void setHandlers()
{
handler.addHandler(&Server::executingFunction);
handler.addHandler(
[](int id, const std::string& message)
{
std::cout << message << id << std::endl;
});
}
static void executingFunction(int id, const std::string& message) {
std::cout << "Listener Worked!" << std::endl;
}
};

boost::asio and multiple client connections using asynch

I need to establish up to three different TCP connections to different servers. All three connections requiring different protocols, different handshakes and different heartbeats. Studying http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp, reading stuff here and following Chris Kohlhoffs advices I tried to implement it as below.
The problem is that with this architecture I'm getting a bad_weak_pointer exception at calling shared_from_this() in doConnect() no matter what I'm doing.
Importent These are just snippets of a not running code, which can contain bugs! Importent
I'm having a base class which is containing some basic methods.
Connection.h
class Connection : public std::enable_shared_from_this<Connection>
{
public:
//! Ctor
inline Connection();
//! Dtor
inline virtual ~Connection();
inline void setReconnectTime(const long &reconnectAfterMilisec)
{
m_reconnectTime = boost::posix_time::milliseconds(reconnectAfterMilisec);
}
inline void setHandshakePeriod(const long &periodInMilisec)
{
m_handshakePeriod = boost::posix_time::milliseconds(periodInMilisec);
}
virtual void doConnect() = 0;
virtual void stop() = 0;
//... and some view more...
}
I have then my three classes which are derived from the base class. Here just one (and also the core part) to depict the approach.
ConnectionA.h
//queues which containing also the age of the messages
typedef std::deque<std::pair<handshakeMsg, boost::posix_time::ptime>> handskMsg_queue;
typedef std::deque<std::pair<errorcodeMsg, boost::posix_time::ptime>> ecMsg_queue;
typedef std::deque<std::pair<A_Msg, boost::posix_time::ptime>> A_Msg_queue;
class ConnectionA : public Connection
{
public:
ConnectionA();
ConnectionA(const std::string& IP, const int &port);
ConnectionA& operator=(const ConnectionA &other);
virtual ~ConnectionA();
virtual void stop() override;
virtual void doConnect() override;
void doPost(std::string &message);
void doHandshake();
void sendErrorCode(const int &ec);
std::shared_ptr<boost::asio::io_service>m_ioS;
private:
std::shared_ptr<tcp::socket> m_socket;
std::shared_ptr<boost::asio::deadline_timer> m_deadlineTimer; // for reconnetions
std::shared_ptr<boost::asio::deadline_timer> m_handshakeTimer; // for heartbeats
void deadlineTimer_handler(const boost::system::error_code& error);
void handshakeTimer_handler(const boost::system::error_code& error);
void doRead();
void doWrite();
std::string m_IP;
int m_port;
handskMsg_queue m_handskMsgQueue;
ecMsg_queue m_ecMsgQueue;
A_Msg_queue m_AMsgQueue;
}
ConnectionA.cpp
ConnectionA::ConnectionA(const std::string &IP, const int &port)
: m_ioS()
, m_socket()
, m_deadlineTimer()
, m_handshakeTimer()
, m_IP(IP)
, m_port(port)
, m_handskMsgQueue(10)
, m_ecMsgQueue(10)
, m_AMsgQueue(10)
{
m_ioS = std::make_shared<boost::asio::io_service>();
m_socket = std::make_shared<tcp::socket>(*m_ioS);
m_deadlineTimer = std::make_shared<boost::asio::deadline_timer>(*m_ioS);
m_handshakeTimer = std::make_shared<boost::asio::deadline_timer> (*m_ioS);
m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
m_handshakeTimer->async_wait(boost::bind(&ConnectionA::handshakeTimer_handler, this, boost::asio::placeholders::error));
}
ConnectionA::~ConnectionA()
{}
void ConnectionA::stop()
{
m_ioS->post([this]() { m_socket->close(); });
m_deadlineTimer->cancel();
m_handshakeTimer->cancel();
}
void ConnectionA::doConnect()
{
if (m_socket->is_open()){
return;
}
tcp::resolver resolver(*m_ioS);
std::string portAsString = std::to_string(m_port);
auto endpoint_iter = resolver.resolve({ m_IP.c_str(), portAsString.c_str() });
m_deadlineTimer->expires_from_now(m_reconnectTime);
// this gives me a bad_weak_pointer exception!!!
auto self = std::static_pointer_cast<ConnectionA>(static_cast<ConnectionA*>(this)->shared_from_this());
boost::asio::async_connect(*m_socket, endpoint_iter, [this, self](boost::system::error_code ec, tcp::resolver::iterator){
if (!ec)
{
doHandshake();
doRead();
}
else {
// don't know if async_connect can fail but set the socket to open
if (m_socket->is_open()){
m_socket->close();
}
}
});
}
void ConnectionA::doRead()
{
auto self(shared_from_this());
boost::asio::async_read(*m_socket,
boost::asio::buffer(m_readBuf, m_readBufSize),
[this, self](boost::system::error_code ec, std::size_t){
if(!ec){
// check server answer for errors
}
doRead();
}
else {
stop();
}
});
}
void ConnectionA::doPost(std::string &message)
{
A_Msg newMsg (message);
auto self(shared_from_this());
m_ioS->post([this, self, newMsg](){
bool writeInProgress = false;
if (!m_A_MsgQueue.empty()){
writeInProgress = true;
}
boost::posix_time::ptime currentTime = time_traits_t::now();
m_AMsgQueue.push_back(std::make_pair(newMsg,currentTime));
if (!writeInProgress)
{
doWrite();
}
});
}
void ConnectionA::doWrite()
{
while (!m_AMsgQueue.empty())
{
if (m_AMsgQueue.front().second + m_maxMsgAge < time_traits_t::now()){
m_AMsgQueue.pop_front();
continue;
}
if (!m_socket->is_open()){
continue;
}
auto self(shared_from_this());
boost::asio::async_write(*m_socket,
boost::asio::buffer(m_AMsgQueue.front().first.data(),
m_AMsgQueue.front().first.A_lenght),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec) // successful
{
m_handshakeTimer->expires_from_now(m_handshakePeriod); // reset timer
m_AMsgQueue.pop_front();
doWrite();
}
else {
if (m_socket->is_open()){
m_socket->close();
}
}
});
}
}
void ConnectionA::deadlineTimer_handler(const boost::system::error_code& error){
if (m_stopped){
return;
}
m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
if (!error && !m_socket->is_open()) // timer expired and no connection was established
{
doConnect();
}
else if (!error && m_socket->is_open()){ // timer expired and connection was established
m_deadlineTimer->expires_at(boost::posix_time::pos_infin); // to reactivate timer call doConnect()
}
}
And finally there is also another class which encapsulate these classes make it more comfortable to use:
TcpConnect.h
class CTcpConnect
{
public:
/*! Ctor
*/
CTcpConnect();
//! Dtor
~CTcpConnect();
void initConnectionA(std::string &IP, const int &port);
void initConnectionB(std::string &IP, const int &port);
void initConnectionC(std::string &IP, const int &port);
void postMessageA(std::string &message);
void run();
void stop();
private:
ConnectionA m_AConnection;
ConnectionB m_BConnection;
ConnectionC m_CConnection;
}
TcpConnect.cpp
CTcpConnect::CTcpConnect()
: m_AConnection()
, m_BConnection()
, m_CConnection()
{}
CTcpConnect::~CTcpConnect()
{}
void CTcpConnect::run(){
[this](){ m_AConnection.m_ioS->run(); };
[this](){ m_BConnection.m_ioS->run(); };
[this](){ m_CConnection.m_ioS->run(); };
}
void CTcpConnect::stop(){
m_AConnection.stop();
m_BConnection.stop();
m_CConnection.stop();
}
void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
m_AConnection = ConnectionA(IP, port);
m_AConnection.setMaxMsgAge(30000);
//... set some view parameter more
m_AConnection.doConnect();
}
// initConnectionB & initConnectionC are quite the same
void CTcpConnect::postMessageA(std::string &message)
{
m_AConnection.doWrite(message);
}
In the beginning I tried also to have only one io_service (for my approach this would be fine), but holding the service just as reference gave some headache, because my implementation requires also a default constructor for the connections. Now each connection has its own io-service.
Any ideas how I can bring this code to run?
Feel free to make suggestion for other architectures. If you could came up this some snippets would be even the better. I'm struggling with this implementation for weeks already. I'm grateful for every hint.
BTW I'm using boost 1.61 with VS12.
This is the problem:
m_AConnection = ConnectionA(IP, port);
That is, ConnectionA derives from Connection which derives from enable_shared_from_this. That means that ConnectionA must be instantiated as a shared pointer for shared_from_this to work.
Try this:
void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
m_AConnection = std::make_shared<ConnectionA>(IP, port);
m_AConnection->setMaxMsgAge(30000);
//... set some view parameter more
m_AConnection->doConnect();
}
EDIT1:
You are right. That was the issue. Now I realised that the way I'm calling io-service.run() is total crap.
It is very uncommon to use more than one io_service, and extremely uncommon to use one per connection :)
However, do you know if I need the cast then calling shared_from_this()? I noticed the asynch_connect() works fine with and without the cast.
Many Asio examples use shared_from_this() for convenience, I for example don't use it in my projects at all. There are certain rules that you need to be careful when working with Asio. For example, one is that the reading and writing buffers must not be destructed before the corresponding callback is executed, if the lambda function captures a shared pointer to the object that holds the buffers, this condition holds trivially.
You could for example do something like this as well:
auto data = std::make_shared<std::vector<uint8_t>>(10);
async_read(socket,
boost::asio::const_buffer(*data),
[data](boost::system::error_code, size_t) {});
It would be valid but would have the performance drawback that you'd be allocating a new data inside std::vector on each read.
Another reason why shared_from_this() is useful can be seen when you look at some some of your lambdas, they often have the form:
[this, self,...](...) {...}
That is, you very often want to use this inside them. If you did not capture self as well, you'd need to use other measures to make sure this has not been destroyed when the handler is invoked.

C++ ASIO: Asynchronous sockets and threading

My application is based on the asio chat example and consists of a client and a server:
- Client: Connect to the server, receive requests and respond to it
- Server: Has a QT GUI (main thread) and a network service (separate thread) listening for connections, sending requests to particular clients and interprets the response from/in the GUI
I want to achieve this in an asynchronous way to avoid a seperate thread for each client connection.
In my QT window, I have one io_service instance and one instance of my network service:
io_service_ = new asio::io_service();
asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), "1234");
service_ = new Service(*io_service_, endpoint, this);
asio::io_service* ioServicePointer = io_service_;
t = std::thread{ [ioServicePointer](){ ioServicePointer->run(); } };
I want to be able to send data to one client, like this:
service_->send_message(selectedClient.id, msg);
And I am receiving and handling the responses via the observer pattern (the window implements the IStreamListener interface)
Service.cpp:
#include "Service.h"
#include "Stream.h"
void Service::runAcceptor()
{
acceptor_.async_accept(socket_,
[this](asio::error_code ec)
{
if (!ec)
{
std::make_shared<Stream>(std::move(socket_), &streams_)->start();
}
runAcceptor();
});
}
void Service::send_message(std::string streamID, chat_message& msg)
{
io_service_.post(
[this, msg, streamID]()
{
auto stream = streams_.getStreamByID(streamID);
stream->deliver(msg);
});
}
Stream.cpp:
#include "Stream.h"
#include <iostream>
#include "../chat_message.h"
Stream::Stream(asio::ip::tcp::socket socket, StreamCollection* streams)
: socket_(std::move(socket))
{
streams_ = streams; // keep a reference to the streamCollection
// retrieve endpoint ip
asio::ip::tcp::endpoint remote_ep = socket_.remote_endpoint();
asio::ip::address remote_ad = remote_ep.address();
this->ip_ = remote_ad.to_string();
}
void Stream::start()
{
streams_->join(shared_from_this());
readHeader();
}
void Stream::deliver(const chat_message& msg)
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
write();
}
}
std::string Stream::getName()
{
return name_;
}
std::string Stream::getIP()
{
return ip_;
}
void Stream::RegisterListener(IStreamListener *l)
{
m_listeners.insert(l);
}
void Stream::UnregisterListener(IStreamListener *l)
{
std::set<IStreamListener *>::const_iterator iter = m_listeners.find(l);
if (iter != m_listeners.end())
{
m_listeners.erase(iter);
}
else {
std::cerr << "Could not unregister the specified listener object as it is not registered." << std::endl;
}
}
void Stream::readHeader()
{
auto self(shared_from_this());
asio::async_read(socket_,
asio::buffer(read_msg_.data(), chat_message::header_length),
[this, self](asio::error_code ec, std::size_t /*length*/)
{
if (!ec && read_msg_.decode_header())
{
readBody();
}
else if (ec == asio::error::eof || ec == asio::error::connection_reset)
{
std::for_each(m_listeners.begin(), m_listeners.end(), [&](IStreamListener *l) {l->onStreamDisconnecting(this->id()); });
streams_->die(shared_from_this());
}
else
{
std::cerr << "Exception: " << ec.message();
}
});
}
void Stream::readBody()
{
auto self(shared_from_this());
asio::async_read(socket_,
asio::buffer(read_msg_.body(), read_msg_.body_length()),
[this, self](asio::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
// notify the listener (GUI) that a response has arrived and pass a reference to it
auto msg = std::make_shared<chat_message>(std::move(read_msg_));
std::for_each(m_listeners.begin(), m_listeners.end(), [&](IStreamListener *l) {l->onMessageReceived(msg); });
readHeader();
}
else
{
streams_->die(shared_from_this());
}
});
}
void Stream::write()
{
auto self(shared_from_this());
asio::async_write(socket_,
asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
[this, self](asio::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
write();
}
}
else
{
streams_->die(shared_from_this());
}
});
}
Interfaces
class IStream
{
public:
/// Unique stream identifier
typedef void* TId;
virtual TId id() const
{
return (TId)(this);
}
virtual ~IStream() {}
virtual void deliver(const chat_message& msg) = 0;
virtual std::string getName() = 0;
virtual std::string getIP() = 0;
/// observer pattern
virtual void RegisterListener(IStreamListener *l) = 0;
virtual void UnregisterListener(IStreamListener *l) = 0;
};
class IStreamListener
{
public:
virtual void onStreamDisconnecting(IStream::TId streamId) = 0;
virtual void onMessageReceived(std::shared_ptr<chat_message> msg) = 0;
};
/*
streamCollection / service delegates
*/
class IStreamCollectionListener
{
public:
virtual void onStreamDied(IStream::TId streamId) = 0;
virtual void onStreamCreated(std::shared_ptr<IStream> stream) = 0;
};
StreamCollection is basically a set of IStreams:
class StreamCollection
{
public:
void join(stream_ptr stream)
{
streams_.insert(stream);
std::for_each(m_listeners.begin(), m_listeners.end(), [&](IStreamCollectionListener *l) {l->onStreamCreated(stream); });
}
// more events and observer pattern inplementation
First of all: The code works as intended so far.
My question:
Is this the way ASIO is supposed to be used for asynchronous programming? I'm especially unsure about the Service::send_message method and the use of io_service.post. What is it's purpose in my case? It did work too when I just called async_write, without wrapping it in the io_service.post call.
Am I running into problems with this approach?
Asio is designed to be a tookit rather than a framework. As such, there are various ways to successfully use it. Separating the GUI and network threads, and using asynchronous I/O for scalability can be a good idea.
Delegating work to the io_service within a public API, such as Service::send_message(), has the following consequences:
decouples the caller's thread from the thread(s) servicing the io_service. For example, if Stream::write() performs a time consuming cryptographic function, the caller thread (GUI) would not be impacted.
it provides thread-safety. The io_service is thread-safe; however socket is not thread-safe. Additionally, other objects may not be thread safe, such as write_msgs_. Asio guarantees that handlers will only be invoked from within threads running the io_servce. Consequently, if only one thread is running the io_service, then there is no possibility for concurrency and both socket_ and write_msgs_ will be accessed in a thread-safe manner. Asio refers to this as an implicit strand. If more than one thread is processing the io_service, then one may need to use an explicit strand to provide thread safety. See this answer for more details on strands.
Additional Asio considerations:
Observers are invoked within handlers, and handlers are running within the network thread. If any observer takes a long time to complete, such as having to synchronize with various shared objects touched by the GUI thread, then it could create poor responsiveness across other operations. Consider using a queue to broker events between the observer and subject components. For instance, one could use another io_service as a queue, that is being ran by its own thread, and post into it:
auto msg = std::make_shared<chat_message>(std::move(read_msg_));
for (auto l: m_listeners)
dispatch_io_service.post([=](){ l->onMessageReceived(msg); });
Verify that the container type for write_msgs_ does not invalidate iterators, pointers and references to existing elements on push_back() and other elements for pop_front(). For instance, using std::list or std::dequeue would be safe, but a std::vector may invalidate references to existing elements on push_back.
StreamCollection::die() may be called multiple times for a single Stream. This function should either be idempotent or handle the side effects appropriately.
On failure for a given Stream, its listeners are informed of a disconnect only in one path: failing to read a header with an error of asio::error::eof or asio::error::connection_reset. Other paths do not invoke IStreamListener.onStreamDisconnecting():
the header is read, but decoding failed. In this particular case, the entire read chain will stop without informing other components. The only indication that a problem has occurred is a print statement to std::cerr.
when there is a failure reading the body.