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 whenmConnection
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.
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