So I've made a socket class that uses boost::asio library to make asynchronous reads and writes. It works, but I have a few questions.
Here's a basic code example:
class Socket
{
public:
void doRead()
{
m_sock->async_receive_from(boost::asio::buffer(m_recvBuffer), m_from, boost::bind(&Socket::handleRecv, this, boost::asio::placeholders::error(), boost::asio::placeholders::bytes_transferred()));
}
void handleRecv(boost::system::error_code e, int bytes)
{
if (e.value() || !bytes)
{
handle_error();
return;
}
//do something with data read
do_something(m_recvBuffer);
doRead(); //read another packet
}
protected:
boost::array<char, 1024> m_recvBuffer;
boost::asio::ip::udp::endpoint m_from;
};
It seems that the program will read a packet, handle it, then prepare to read another. Simple.
But what if I set up a thread pool? Should the next call to doRead()
be before or after handling the read data? It seems that if it is put before do_something()
, the program can immediately begin reading another packet, and if it is put after, the thread is tied up doing whatever do_something()
does, which could possibly take a while. If I put the doRead()
before the handling, does that mean the data in m_readBuffer
might change while I'm handling it?
Also, if I'm using async_send_to()
, should I copy the data to be sent into a temporary buffer, because the actual send might not happen until after the data has fallen out of scope? i.e.
void send()
{
char data[] = {1, 2, 3, 4, 5};
m_sock->async_send_to(boost::buffer(&data[0], 5), someEndpoint, someHandler);
} //"data" gets deallocated, but the write might not have happened yet!
Additionally, when the socket is closed, the handleRecv will be called with an error indicating it was interrupted. If I do
Socket* mySocket = new Socket()...
...
mySocket->close();
delete mySocket;
could it cause an error, because there is a chance that mySocket
will be deleted before handleRecv()
gets called/finished?
Lots of questions here, I'll try to address them one at a time.
But what if I set up a thread pool?
The traditional way to use a thread pool with Boost.Asio is to invoke io_service::run()
from multiple threads. Beware this isn't a one-size-fits-all answer though, there can be scalability or performance issues, but this methodology is by far the easiest to implement. There are many similar questions on Stackoverflow with more information.
Should the next call to doRead be before or after handling the read data? It seems that if it is put before do_something(), the program can immediately begin reading another packet, and if it is put after, the thread is tied up doing whatever do_something does, which could possibly take a while.
This really depends on what do_something()
needs to do with m_recvBuffer
. If you wish to invoke do_something()
in parallel with doRead()
using io_service::post()
you will likely need to make a copy of m_recvBuffer
.
If I put the doRead() before the handling, does that mean the data in m_readBuffer might change while I'm handling it?
as I mentioned previously, yes this can and will happen.
Also, if I'm using
async_send_to()
, should I copy the data to be sent into a temporary buffer, because the actual send might not happen until after the data has fallen out of scope?
As the documentation describes, it is up to the caller (you) to ensure the buffer remains in scope for the duration of the asynchronous operation. As you suspected, your current example invokes undefined behavior because data[]
will go out of scope.
Additionally, when the socket is closed, the
handleRecv()
will be called with an error indicating it was interrupted.
If you wish to continue to use the socket, use cancel()
to interrupt outstanding asynchronous operations. Otherwise, close()
will work. The error passed to outstanding asynchronous operations in either scenario is boost::asio::error::operation_aborted
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With