Home > Back-end >  Call async_write from outside connect handler in asio
Call async_write from outside connect handler in asio

Time:10-07

I use the asio library to make TCP connections. The async read/write operation is made from the handler function of async_accept.

        mAcceptor.async_accept(*(mConnection->Socket()),
                boost::bind(&TCPConnection::HandleAcceptForWrite, this, 
                pI8Msg, 
                boost::asio::placeholders::error));
void TCPConnection::HandleAcceptForWrite(
        INT8* pI8Msg, 
        const boost::system::error_code& err)
{
    if (!err) {
        TransmitData(pI8Msg);//--> Want to call this fn from outside the handler
    }
    SocketAcceptConnection(pI8Msg);
}

I want to avoid the call to TransmitData (async_write) from within the handler.

I intend to call write from anywhere outside the handler of accept. When I do this I get the error - 'Bad file descriptor'

Is it always necessary to do an async write from within the handler? Please share any code sample if it can be called from elsewhere.

CodePudding user response:

You should provide the necessary context. E.g. what is mConnection? Why does mConnection->Socket() return a pointer? If this is a server, why does it only have a single mConnection?

Spelunking With The Crystal Ball

Using my crystal ball, I'm going to answer this question with

  • mConnection is a shared pointer to an object encapsulating a socket and some connection state
  • It is initialized with a new instance before accepting, always
  • Therefore, unless something else shares ownership of *mConnection it will by definition be destroyed when mConnection is assigned a new instance.

The above combined, there's only one reasonable explanation: mConnection points to a type T that derives from enable_shared_from_this<T> so that it can share ownership with itself. You should be able to see this inside the TransmitData function where the shared pointer should be captured in a bind expression (or lambda) to fulfill the async_read completion handler.

What this does is: keep the connection alive, in C terms: extend or guarantee the lifetime until that completion handler returns. The completion handler in turn may initiate more work that shares the ownership (captures the shared pointer), and so on, until the last operation that owns the connection object loses interest and the connection object (T) is freed.


What To Do?

You need to keep the connection alive, even if it is idle. There are many ways. You could insert the shared pointer to it into a "connection table" (e.g. std::vector<shared_ptr<Connection> >). The downside would be that it then becomes hard to prune the connections: the connections are always owned, so never freed. (See weak_ptr below!)

In practice I'd make the connection (let's call the type Connection instead of T from now) responsible: it may decide when the other side hung up, or when there was an exchange that tells the connection to close, or perhaps even a timeout has reached.

Because the latter is very common (connections often close automatically after a set period of idle time), and also most flexible (you can set the expiration to "infinite"), let's show this:

Side Note: during my effort to expand your snippet into something working, I noticed that weirdly the acceptor appears to live INSIDE the connection type? That makes no sense because how can you have multiple connections with a single acceptor ("server")? So I changed things around to be more typical

Note that I still keep a table of connections (mConnections) but instead of storing owning pointers (like shared_ptr<>) I store weak_ptr<> so that we can observe the connections without altering their lifetime.

enter image description here

Text Output

For easy reference (taken from http://coliru.stacked-crooked.com/a/def4f1927c7b975f):

  • Server

     ./a.out &
     HandleAccept: 127.0.0.1:43672
     HandleAccept: 127.0.0.1:43674
     HandleAccept: 127.0.0.1:43676
     TCPConnection timeout, closing 127.0.0.1:43672
     TCPConnection::~TCPConnection()
     TCPConnection timeout, closing 127.0.0.1:43674
     TCPConnection::~TCPConnection()
     TCPConnection timeout, closing 127.0.0.1:43676
     TCPConnection::~TCPConnection()
     HandleAccept: 127.0.0.1:43678
     HandleAccept: 127.0.0.1:43680
     HandleAccept: 127.0.0.1:43682
     Sending 'HELLO WORLD
     '
     TransmitData on 127.0.0.1:43678
     TransmitData on 127.0.0.1:43680
     TransmitData on 127.0.0.1:43682
     OnWritten: SuccessHELLO WORLD
      (12 bytes)
     OnWritten: Success (12 bytes)
     OnWritten: Success (12 bytes)
     TCPConnection timeout, closing 127.0.0.1:43678
     TCPConnection::~TCPConnection()
     TCPConnection timeout, closing 127.0.0.1:43680
     TCPConnection::~TCPConnection()
     TCPConnection timeout, closing 127.0.0.1:43682
     TCPConnection::~TCPConnection()
    
  • Clients

     sleep 1
     (for a in {1..3}; do netcat 127.0.0.1 7979& done; time wait)
    
     real    0m1.011s
     user    0m0.012s
     sys 0m0.004s
    
     sleep 0.5
     (for a in {1..3}; do netcat 127.0.0.1 7979& done; time wait)
     HELLO WORLD
     HELLO WORLD
     HELLO WORLD
    
     real    0m1.478s
     user    0m0.008s
     sys 0m0.008s
    
  • Related