It is possible to have a UDP socket concurrently receiving from one remote endpoint and sending to a different remote endpoint. However, per the Boost.Asio Threads and Boost.Asio documentation, it is generally unsafe to make concurrent calls on a single object.
Thus, this is safe:
thread_1 | thread_2
--------------------------------------+---------------------------------------
socket.async_receive_from( ... ); |
socket.async_send_to( ... ); |
and this is safe:
thread_1 | thread_2
--------------------------------------+---------------------------------------
socket.async_receive_from( ... ); |
| socket.async_send_to( ... );
but this is specified as not being safe:
thread_1 | thread_2
--------------------------------------+---------------------------------------
socket.async_receive_from( ... ); | socket.async_send_to( ... );
|
Be aware that some functions, such as boost::asio::async_read
, are a composed operation, and have additional thread safety restrictions.
If either of the following are true, then no additional synchronization needs to occur, as the flow will be implicitly synchronous:
- All socket calls occur within handlers, and
io_service::run()
is only invoked from a single thread.
async_receive_from
and async_send_to
are only invoked within the same chain of asynchronous operations. For example, the ReadHandler
passed to async_receive_from
invokes async_send_to
, and the WriteHandler
passed to async_send_to
invokes async_receive_from
.
void read()
{
socket.async_receive_from( ..., handle_read ); --.
} |
.-----------------------------------------------'
| .----------------------------------------.
V V |
void handle_read( ... ) |
{ |
socket.async_send_to( ..., handle_write ); --. |
} | |
.-------------------------------------------' |
| |
V |
void handle_write( ... ) |
{ |
socket.async_receive_from( ..., handle_read ); --'
}
On the other hand, if there are multiple threads potentially making concurrent calls to the socket, then synchronization needs to occur. Consider performing synchronization by either invoking the functions and handlers through a boost::asio::io_service::strand, or using other synchronization mechanisms, such as Boost.Thread's mutex.
In addition to thread safety, the management of object lifetimes must be considered. If the server needs to process multiple request concurrently, then be careful about ownership of the buffer
and endpoint
for each request->process->response chain. Per async_receive_from
's documentation, the caller retains ownership of both the buffer and endpoint. As such, it may be easier to manage the lifetime of the objects via boost::shared_ptr. Otherwise, if the chain is quick enough that concurrent chains are not required, then it simplifies management, allowing the same buffer and endpoint to be used per request.
Finally, the socket_base::reuse_address
class allows a socket to be bound to an address that is already in use. However, I do not think it is an applicable solution here, as it is generally used:
- For TCP to allow a process to restart and listen to the same port, even if the port is in a
TIME_WAIT
state.
- For UDP to allow multiple processes to bind to the same port, allowing each process to receive and broadcast via multicast.