Home > Software design >  Receive UDP messages without allocating new buffer
Receive UDP messages without allocating new buffer

Time:04-26

When trying to use UdpClient.Receive I noticed that it just returns a byte array. Does this allocate a new buffer for every packet? As far as I know, allocating a new byte array for every received packet is bad for performance and memory usage. To get around that same problem when using Tcp, I used an ArrayPool to rent the buffers. But how would I do that with Udp?

I thought about using the same method like with Tcp and just using a socket, but udp is not stream based and therefore that approach makes little sense (I don't know how big one packet will be and I cannot continually read from a stream like in Tcp).

TL;DR:

How do I save performance when receiving UDP packets while still always receiving the full packet?

CodePudding user response:

When trying to use UdpClient.Receive I noticed that it just returns a byte array. Does this allocate a new buffer for every packet?

Typically, yes.

Internally, UdpClient uses a fixed-sized byte[65536] array to read each packet. If a received packet is exactly that size, the internal buffer is returned as-is. However, if the received packet is less than that size, which is usually the case, then it allocates a new byte[] of the actual packet size, copies the internal buffer's data into that new array, and then returns it.

I don't know how big one packet will be

It is possible in the Winsock API to determine the next packet's actual size without extracting the packet from the socket's queue. Winsock's recvfrom() function has a MSG_PEEK flag for that purpose.

UdpClient.Receive() does not support that flag, however the UdpClient's underlying Socket does in its ReceiveFrom() method, eg:

byte[] ignore = new byte[0]; // the buffer parameter of ReceivFrom() can't be null!
EndPoint ep;

int size = UdpClient.Client.ReceiveFrom(ignore, SocketFlags.Peek, ref ep);
// obtain a buffer of sufficient size ... 
UdpClient.Client.ReceiveFrom(buffer, size, SocketFlags.None, ref ep);

Although Socket.ReceiveFrom() does allow a 0-length byte[] array (it will pass a NULL pointer and 0 buffer length to Winsock's recvfrom()), what I'm not sure about is whether Socket.ReceiveFrom() will actually return the next packet size, or if it will throw a SocketException if you try to peek into the queue using a byte[] array that is smaller than the actual packet (as I am not sure whether or not recvfrom() reports a WSAEMSGSIZE error when the MSG_PEEK flag is used).

To avoid this potential issue, keep reading ...

How do I save performance when receiving UDP packets while still always receiving the full packet?

Rather then trying to pool arrays, I would suggest you simply use a single 64K byte[] array (like UdpClient does internally), since UDP packets will never exceed that size. Simply use UdpClient.Client.ReceiveFrom() directly instead of using UdpClient.Receive(), eg:

byte[] myBuffer = new byte[65536];
EndPoint ep;

...

int received = UdpClient.Client.ReceiveFrom(myBuffer, ref ep);
// use myBuffer up to received bytes ...
// use (IPEndPoint)ep if needed ...

... 

This way, you can re-use a single byte[] array for multiple reads (just as UdpClient does internally).

  • Related