I have an UDP socket that will receive some packets, of potentially different sizes, and I handle this asynchronously:
socket.async_receive_from(boost::asio::buffer(buffer, 65536), senderEndpoint, handler);
The problem here is that to handle the different sizes I have a big buffer, something that could be addressed with variable size buffers.
To my understanding, when using async_receive_from
, the handler is called with only one packet at a time, because the packet boundaries are preserved in UDP. So, is there a way to give an empty buffer to async_receive_from
that Asio will grow to fit the packet size ?
Also note that I wrap packets, so for every packet transiting to this socket, the 4 first bytes are the length of the packet.
For a more precise answer, here is a detailed and explained code.
First, we need to call the receive handler without filling a buffer. This is done using boost::asio::null_buffer()
(see reactor-style operations for more information, as stated by Tanner).
void UDPConnection::receive()
{
socket.async_receive(boost::asio::null_buffers(), receive_handler);
}
Now, when a packet will be received, receive_handler
will be called without any buffer to be filled.
Now, for the handler:
void UDPConnection::handleReceive(const boost::system::error_code& error, unsigned int)
{
// Standard check: avoid processing anything if operation was canceled
// Usually happens when closing the socket
if(error == boost::asio::error::operation_aborted)
return;
With socket.available()
, we can get the number of bytes to be read. Important: this number is not necessarily the size of the packet! For my tests, this number was always greater than the packet size (even with 8 kB packets, which was the largest my computer could handle).
// Prepare buffer
unsigned int available = socket.available();
unsigned char* buffer = new unsigned char[available];
Here the magic happens: the "real" receive call is done here, and will normally be fast, since it will just fill a buffer. This call will only dequeue one packet (even if at the time of the call there was more than one in the socket). The return value is important here, as only a part of the buffer may have been filled. Ex: available=50, packetSize=13.
// Fill it
boost::asio::ip::udp::endpoint senderEndpoint;
boost::system::error_code ec;
unsigned int packetSize = socket.receive_from(boost::asio::buffer(buffer, available), senderEndpoint, 0, ec);
Now, just standard error-checking / processing / etc...
if(ec)
{
// ...
}
// Process packet
// ...
receive();
}
You could do that by hand.
my_socket.available()
returns the size of the next Udp packet waiting in line at the socket to be read. So you could use that to check how big the next packet is, grow your buffer accordingly and then receive it. However, i agree with the comenters that that is most likely less efficient than just using the biggest possible size as buffer. Unless maybe the maximum is a lot larger than the average packet and is so unlikely that your app will often not have to receive it at all.
But that is a question for optimization. The answer to your question is available()
and growing the buffer yourself.
edit: I am aware that this is less than ideal in an async situation as available()
only returns the size of the next udp packet if it is already waiting when available()
is called.