3
votes

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.

2
Why? Just specify the largest buffer that you need. The receive method will tell you how many bytes were really received.user207421
This program will run on mobile platform, not pc - so its memory footprint should be lightweight. I think there can be a more efficient way than allocating a big buffer.Synxis
Allocating one big buffer once is a lot more efficient than allocating a lot of smaller buffers.user207421
You can lazily allocate the properly sized buffer via null_buffers. Also, the first 4 bytes containing the length is likely unnecessary, as available() returns the size of the datagram available for reading, and receive() will dequeue a max of one datagram.Tanner Sansbury

2 Answers

12
votes

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();
}
1
votes

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.