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 and may only be used if you agree to the BSD License Terms.

Advertisements

About Bogdan Varlamov

A .NET Software Engineer that strongly believes technology should simplify and improve the quality of our lives instead of making them more complicated. View all posts by Bogdan Varlamov

5 responses to “Minimize Asynchronous Socket Callbacks With Dynamic Buffer Sizes

  • Sven

    Looks like my previous question got messed up.

    Here goes the second try:

    On line 129 you check if the Available count is less then zero… If it is indeed zero, you instantiate the receive buffer to a negative length… Should the check be “greater than zero”?

    regards

  • Bogdan Varlamov

    You are absolutely correct. I think the less than and greater than signs were removed when I posted my code originally, and I added the wrong one in when I noticed they were gone earlier.

    Nice catch!

  • bahi

    i’we read your code, and you use tcpListener, but you do not need it. you could simple call an async receive on the socket class (like MS example) and use there your 0 byte buffer, and then use the same call to get the data with a correct buffer size.

    anyhow, i see 2 calls, even on situations when a small buffer and a single async call would be enough.

    your idea is good, so maybe a good solution is to dynamically resize a buffer (with available * 1,2 size) to avoid running into a situation where initialization of 2 async read operation is necessary…

    also recreating a buffer for the corrrect amount of data is still done with an allocation and deallocation (new / GC). if you would use 1 buffer (pre allocated – dynamically resized for the given connection) – you could gain more perf.

    • Bogdan Varlamov

      @bahi,

      I’m not sure I understand what you mean. I am using a TcpListener in order to accept socket connections. I believe it is fairly standard practice to listen for connections in such a way… can you elaborate on what you mean by saying that I do not need it?

      I’m also not sure what you mean when you talk about dynamically resizing buffers, since the buffer is just a byte array and is defined/created in a fixed size (unlike, say, a List object).

      I’m sure you have valid and interesting points, and I would love to feel like I understand what you are trying to get across–could you possibly post some source code demonstrating what you are saying?

      I’d certainly appreciate it! Thanks for the comment 🙂

  • Chris Watts

    at some point, shouldnt you call socket.endreceive(ASyncResult)??

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: