/Software Development

Minimize Asynchronous Socket Callbacks With Dynamic Buffer Sizes

I’ve been doing a lot of work with .NET Sockets lately, and as I was thinking about the specific needs of my application I got an idea about a more optimal way to use sockets. In this article, I want to talk about the concept of reading a Socket asynchronously, raising events to notify your application when bytes are received, and minimizing the number of times the callback method is processed.

If you are familiar with .NET Sockets you might already know that there are asynchronous methods to receive bytes from Sockets (Microsoft has an Asynchronous Server Socket Example available if you are interested in seeing code for doing this).

In the above code sample, a StateObject class that contains a byte buffer with a static size is passed around. When the asynchronous socket read happens, incoming bytes are stored into this buffer and then passed back to the receive callback method. This example works fine, but I think there are some optimizations that can be applied.

To see where these optimizations can be applied let’s think about what happens in the typical Socket reading application, but with real world examples. Let’s pretend that our task is to pick up items that arrive at a train station and take them to our home.

In the Microsoft example this would look something like this:

You wait at the train station with a bag that can hold 1024 items. 3 items arrive, you stick them into your bag, then run home to drop them off. Then, you come back to the train station and wait for more items.

If 8000 items arrive, you put 1024 into your bag and take them home, then come back and put the next 1024 into the bag, take them home, and repeat until all of the items are home.

An alternative to this would be to just grab all of the items that arrive, stick them into a bag that holds exactly that amount of items, and bring them back once.

I haven’t found a way to do exactly that in .NET Sockets, so the next best thing I have come up with would look like this:

You wait at the train station with a bag that can hold 0 items. 3 items arrive, you run home, get a bag that can hold 3 items, run back to the train station and fill your bag. Then, you bring the items home, get a bag that can hold 0 items and return to the train station to wait again.

At first this sounds inefficient…you have to switch out bags and run back and forth twice! Seems wasteful, but when you think about it you will notice that no matter how many items arrive you can always grab all items in just two trips.

So, what? It costs more to allocate memory for the new buffers every time than to keep reading into the same one!

While that may be true, when buffering into a fixed size buffer you will inevitably have some “left over space” in the buffer. So, you may be receiving into a 1024 byte buffer, but you only receive 1000 bytes. Now you have 24 empty bytes that are being wasted…And you will undoubtedly have to “trim up” your buffer before using the received bytes in the application.

In most cases you will probably do an Array.Copy to move over the bytes with real data into a separate byte[] of the appropriate size—allocating space for the new array, and using twice the memory during the copy operation!

In theory it might be less costly to reuse the same buffer, but in practice it might actually be more efficient to create the properly sized array in the beginning. Your application is going to do something with the received data, so in most cases the bytes need to be in a usable state…

I’m not advocating that you use my model for all of your socket communications logic, but there are some places where it might especially make sense to do so…

When your application:

  • receives greatly varying amounts of data (e.g. an app that receives a periodic and small keep-alive message, while also receiving large image files)
  • performs intensive processing each time data is received (e.g. received bytes are being buffered and scanned for application specific protocols)
  • receives large chunks of data intermittently

I’ve got some sample code to illustrate what my train station example would look like translated into C#:

//will be used to listen for incoming channel connections TcpListener _tcpListener;

//socket will be used to determine connectivity/etc. Socket _incomingSocket;

//will control the socket buffer //NOTE: if you implement some real-time network traffic analysis //         or are using an application level protocol and know the min. //         size of a message, adjust this value as appropriate! int _incomingSocketByteBufferSize = 0;

public bool StartIncomingCommunications() {

bool returnValue = false;//assume problems

try {

//start listening _tcpListener.Start();

//let us accept the connection when we get it _tcpListener.BeginAcceptSocket(

new AsyncCallback(DoAcceptIncomingSocketConnection), null);

}

catch (Exception caught) {

HandleLogging(caught);

}

return returnValue;

}

//the callback to handle the incoming socket connection private void DoAcceptIncomingSocketConnection(IAsyncResult iaResult) {

try {

//this application only allows one socket connection at a time //so we need to check that our socket variable isn't already being used

if (_incomingSocket != null) {

try {

//we already connected by some socket //we will close the current connection and establish a new one //an alternative would be to reject the incoming socket _incomingSocket.Close();

_incomingSocket = null;

}

catch (Exception caught) {

HandleLogging(caught);

}

}

// End the operation and get the socket _incomingSocket = _tcpListener.EndAcceptSocket(iaResult);

//create a byte buffer to store received information //this buffer is of size 0 by default and serves as a way to notify when bytes are received byte[] readingBuffer = new byte[_incomingSocketByteBufferSize];

//the last parameter in this method should be a custom object //i'm just passing along my readingBuffer since i only have one socket //and don't care about anything other than the buffer _incomingSocket.BeginReceive(readingBuffer, 0, readingBuffer.Length, SocketFlags.None, new AsyncCallback(DoReceiveCallback), readingBuffer);

NotifyOfConnectivityStatus();

//start accepting again //this will allow us to process new connections //an alternative would be to only do this once the current connection is closed _tcpListener.BeginAcceptSocket(

new AsyncCallback(DoAcceptIncomingSocketConnection), null);

}

catch (Exception caught) {

HandleLogging(caught);

}

}

/// this method processes the receive callback private void DoReceiveCallback(IAsyncResult iaResult) {

try {

byte[] buffer = (byte[]])iaResult.AsyncState;//extract our original buffer

if (buffer.Length == 0)//this buffer was used as a notification of bytes received { //we will replace our "zero bag" with the appropriately sized one

buffer = new byte[_incomingSocket.Available]; }

else//read real data (got our appropriately sized bag) { //some method that does something with the bytes we just got... OnBytesReceivedOverIncomingChannel(buffer);

//maybe more bytes arrived while we've been sitting here? if (_incomingSocket.Available > 0)//still bytes available for reading {

buffer = new byte[_incomingSocket.Available];//get the bag for the bytes just arrived }

else//done, go to notification cycle (get the zero-sized bag) { buffer = new byte[_incomingSocketByteBufferSize]; } }

try //MSDN recomends trying to read from a socket and handling exceptions as a way {   //of detecting when the connection is broken.. //go back and wait for more data (go back to the train station) _incomingSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(DoReceiveCallback), buffer);

}

catch (SocketException sckEx) { if (sckEx.SocketErrorCode == SocketError.ConnectionReset)//socket conn got closed... { NotifyOfConnectivityStatus(); }

else //some other weird error { throw sckEx; //pass it up to the higher try/catch }

}

}

catch (Exception caught) {

HandleLogging(caught);

}

}

To see that code work you have to paste it into your own class, and add your own methods for HandleLogging, and NotifyOfConnectionStatus, etc.

The above source code is © 2008 by Bogdan Varlamov under standard BSD license.

Bogdan Varlamov

Bogdan Varlamov

I believe technology makes life more worthwhile :)

Read More