Communicating over Sockets: Blocking vs. Unblocking

Welcome to this installment of the .NET Nuts & Bolts column. One of the prior articles—"Multithreading in .NET Applications, Part 3"—contained a sample multithreaded listener application and client. I've received a fair number of questions surrounding this and requesting more specifics about socket communication. The focus of this article will be to address the topic of sockets in more detail. Specifically, it will focus on blocking versus unblocking sockets. As an added bonus, the solution will include the use of generics and a sample socket connection pool.

Sample Task

To effectively illustrate this topic, you'll need a sample task to perform. The sample task involves creating a socket-based communicator for sending data to a 3rd party system. For the sake of this example, assume the third-party system accepts connections on a specified port number and takes requests in the form of XML fragments and responds in kind. Once a socket connection is opened, it remains opened until it times out due to inactivity, the client closes the connection, or the 3rd party system is shut down. There are a predetermined number of XML fragments that have been identified and serve as business methods for calling into the 3rd party system.

Sample Threaded Server

You'll create a sample threaded server to emulate the behavior of the 3rd party application with which you are going to exchange information. In this case, it will only expose a single method of "uptime," which will accept an XML request of <uptime></uptime> and respond with the amount of time the system has been alive. Any other requests will generate an exception.

Sample Threaded Server

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Xml;

namespace CodeGuru.SocketSample
{
   /// <summary>
   /// Sample Server to emmulate a 3rd party server that uses sockets.
   /// </summary>
   class ThreadedServer
   {
      class ConnectionInfo
      {
         public Socket Socket;
         public Thread Thread;
      }

      private int portNumber;
      private List<ConnectionInfo> connections =
         new List<ConnectionInfo>();
      private Thread acceptThread;
      private Socket serverSocket;

      public ThreadedServer(int port)
      {
         this.portNumber = port;
      }

      public void Start()
      {
         // Set up the socket
         // Resolving local machine information
         IPHostEntry localMachineInfo =
            Dns.GetHostEntry(Dns.GetHostName());
         IPEndPoint myEndPoint =
            new IPEndPoint(localMachineInfo.AddressList[0],
                           this.portNumber);

         // Create the socket, bind it, and start listening
         serverSocket =
            new Socket(myEndPoint.Address.AddressFamily,
                       SocketType.Stream, ProtocolType.Tcp);
         serverSocket.Bind(myEndPoint);
         serverSocket.Listen((int)SocketOptionName.MaxConnections);

         Console.WriteLine("Listening on port " +
                           this.portNumber.ToString());
         acceptThread = new Thread(AcceptConnections);
         acceptThread.IsBackground = true;
         acceptThread.Start();
      }

      private void AcceptConnections()
      {
         while (true)
         {
            // Accept a connection
            Socket socket = serverSocket.Accept();
            ConnectionInfo connection = new ConnectionInfo();
            connection.Socket = socket;

            // Create the thread for the receive.
            connection.Thread = new Thread(ProcessConnection);
            connection.Thread.IsBackground = true;
            connection.Thread.Start(connection);

            // Store the socket in the open connections
            lock (this.connections) this.connections.Add(connection);
         }
      }

      private void ProcessConnection(object stats)
      {
         ConnectionInfo connection = (ConnectionInfo)stats;
         byte[] buffer = new byte[4000];
         System.Text.StringBuilder input =
            new System.Text.StringBuilder();
         try
         {
            while (true)
            {
               int bytesRead = connection.Socket.Receive(buffer);
               if (bytesRead > 0)
               {
                  input.Append(System.Text.Encoding.ASCII.
                               GetString(buffer));

                  // Parse the message
                  XmlDocument xmlInput = new XmlDocument();
                  xmlInput.LoadXml(input.ToString());

                  // Send back a preset response based on the type
                  XmlDocument xmlOutput = new XmlDocument();
                  if (xmlInput.DocumentElement.Name.Equals("uptime"))
                  {
                     xmlOutput.Load(@"C:\uptimeresponse.xml");
                  }
                  else
                  {
                     // Logic to generate desired exception here
                  }
                  connection.Socket.Send(System.Text.Encoding.
                                         ASCII.GetBytes(
                                          xmlOutput.DocumentElement.
                                          OuterXml));
               }
               else if (bytesRead == 0) return;
            }
         }
         catch (SocketException socketExec)
         {
            Console.WriteLine("Socket exception: " +
                              socketExec.SocketErrorCode);
         }
         catch (Exception exception)
         {
            Console.WriteLine("Exception: " + exception);
         }
         finally
         {
            connection.Socket.Close();
            lock (this.connections)
               this.connections.Remove(connection);
         }
      }
   }
}

Now that you've created your class to provide a threaded server, you create a console application to start an instance of it. The main method contains the following code to start an instance of the server for testing:

ThreadedServer ts = new ThreadedServer(8080);
ts.Start();
Console.ReadLine();

Here is the sample XML document to be used as the return from the server:

<uptime>
   <description>System uptime</description >
   <Status>
      <code>OK</code>
      <command>uptime</command>
      <duration>0.127</duration>
      <Times>
         <submitted>20060320151107.409</submitted>
         <completed>20060320151107.566</completed>
      </Times>
   </Status>
</uptime>

Communicating over Sockets: Blocking vs. Unblocking

Sample Socket Connection Pool

You know the 3rd party system allows socket connections to remain open. In an effort to optimize performance, it is desired to have a connection pool of sockets open to the 3rd party system similar in concept to a database connection pool. The trailing sample code will provide you with a simple connection pooling object for your use. It will handle open and closing connections appropriately.

Sample Client Connection Pool Class

Thanks to the release of .NET 2.0, creating a socket connection pool isn't as difficult as it would have been in the past. Generics have introduced much of the plumbing that is required to accomplish the task through the System.Collections.Generic.Queue<T> object. You'll treat your socket connection pool as if it is a queue where you get connections from the queue and put connections back into the queue when you're complete.

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

namespace CodeGuru.SocketSample
{
   /// <summary>
   /// Connection pool of sockets.
   /// </summary>
   public static class SocketConnectionPool
   {
      /// <summary>The maximum size of the connection pool.</summary>
      private static readonly int POOL_SIZE = 20;
      /// <summary>The default port number for the
      /// connection.</summary>
      private static readonly int DEFAULT_PORT = 8080;
      /// <summary>Queue of available socket connections.</summary>
      private static Queue<Socket> availableSockets =
         new Queue<Socket>();

      /// <summary>
      /// Get an open socket from the connection pool.
      /// </summary>
      /// <returns>Socket returned from the pool or new socket
      /// opened.</returns>
      public static Socket GetSocket()
      {
         if (SocketConnectionPool.availableSockets.Count > 0)
         {
            Socket socket = null;
            while( SocketConnectionPool.availableSockets.Count > 0 )
            {
               socket = SocketConnectionPool.availableSockets.Dequeue();
               if (socket.Connected)
               {
                  return socket;
               }
            }
         }
         return SocketConnectionPool.OpenSocket();
      }

      /// <summary>
      /// Return the given socket back to the socket pool.
      /// </summary>
      /// <param name="socket">Socket connection to return.</param>
      public static void PutSocket(Socket socket)
      {
         if (SocketConnectionPool.availableSockets.Count <
             SocketConnectionPool.POOL_SIZE)
         {
            if (socket != null)
            {
               if (socket.Connected)
               {
                  // Set the socket back to blocking and enqueue
                  socket.Blocking = true;
                  SocketConnectionPool.availableSockets.Enqueue(socket);
               }
            }
         }
         else
         {
            // Number of sockets is above the pool size, so just
            // close it.
            socket.Close();
         }
      }

      /// <summary>
      /// Open a new socket connection.
      /// </summary>
      /// <returns>Newly opened socket connection.</returns>
      private static Socket OpenSocket()
      {
         IPHostEntry localMachineInfo  = Dns.GetHostEntry("127.0.0.1");
         IPHostEntry remoteMachineInfo = Dns.GetHostEntry("127.0.0.1");
         IPEndPoint serverEndpoint =
            new IPEndPoint( remoteMachineInfo.AddressList[0],
                            SocketConnectionPool.DEFAULT_PORT);
         IPEndPoint myEndpoint =
            new IPEndPoint( localMachineInfo.AddressList[0], 0);
         Socket socket =
            new Socket( myEndpoint.Address.AddressFamily,
                        SocketType.Stream, ProtocolType.Tcp);
         socket.Connect(serverEndpoint);
         return socket;
      }
   }
}

Blocking vs. Unblocking

Now that you have your sample server and connection pool, you can create your client. A common stumbling block working with sockets and the main point of this article is blocking vs. unblocking sockets. Many examples demonstrate the use of blocking sockets, but don't really describe the behavior as blocking; this causes confusion. Blocking is where the receiving party blocks all activity while it waits to receive input from the socket. Sounds logical enough, but there are issues when the sending party is sending variant output and the receiver doesn't know all information has been sent.

Blocking Socket Sample

The following sample demonstrates the use of blocking sockets. You have to create a byte array of a specified length to read in the input. The byte array is a fixed size, so it may take multiple reads to read all of the input back from the socket connection. Thus, you do the Receive() method in a loop until all of the input is read from the socket. Here is some example code to illustrate the point.

Socket socket = null;
try
{
   // Send the request
   socket = SocketConnectionPool.GetSocket();
   socket.Send(System.Text.Encoding.ASCII.GetBytes("<uptime></uptime>"));
   // Read the response on a non-blocking socket
   System.Text.StringBuilder output = new System.Text.StringBuilder();
   byte[] buffer = new byte[256];
   int bytesRead = 0;
   string bufferString = "";
   do
   {
      bytesRead = socket.Receive(buffer, buffer.Length, 0);
      bufferString = System.Text.Encoding.ASCII.GetString(buffer);
      output.Append(bufferString);
   }
   while (bytesRead > 0);

   // Display the response
   Console.WriteLine("Output: " + output.ToString());
}
finally
{
   SocketConnectionPool.PutSocket(socket);
}

If you run this example, you will proceed through the loop a number of times based on the size of the output you are returning from the sample server. With the sample provided, it should only take a single read. However, the loop will try a second read and get stuck on the Receive method. That occurs because the server has stopped sending bytes, but the client is blocking and doesn't have any way to know the server is actually done sending. You could set the timeout on the client connection to a low interval, but that is not ideal in case the request takes a bit to respond. The server could be made to close the connection to indicate when it is done, but in this sample the server is a 3rd party and outside your control.

Unblocking Socket Sample

The following sample code demonstrates the use of a nonblocking socket. It gets around the prior issue where the client ends up in an indefinite state of waiting on a response. The key difference is in the Receive method. The nonblocking version will return immediately from the receive call, whereas the prior version would wait indefinitely for output. The nonblocking call to Receive will read whatever is available on the socket at that point in time. Thus, you'll read a little bit differently. You'll read in an infinite loop until the desired end tag is located from the XML. If something happens along the way, such as the server shutting down, the read will error out anyway and break the loop.

Socket socket = null;
try
{
   // Send the request
   socket = SocketConnectionPool.GetSocket();

   socket.Send(System.Text.Encoding.ASCII.GetBytes("<uptime></uptime>"));

   // Read the response on a non-blocking socket
   System.Text.StringBuilder output = new System.Text.StringBuilder();
   string bufferString = "";
   socket.Blocking = false;
   while (true)
   {
      if (socket.Poll(1000, SelectMode.SelectRead))
      {
         byte[] buffer = new byte[socket.Available];
         socket.Receive(buffer, SocketFlags.None);
         bufferString = System.Text.Encoding.ASCII.GetString(buffer);
         output.Append(bufferString);
      }

      // Check if we've received the entire response
      if (bufferString.IndexOf("</uptime>") != -1) break;
   }

   // Display the response
   Console.WriteLine("Output: " + output.ToString());
}
finally
{
   SocketConnectionPool.PutSocket(socket);
}

Other Considerations

There is additional functionality that could be added to this approach to make it more usable. An object model could be developed. You could establish a base class from which all business methods will inherit. A communicator class can be created to accept types based on the base business method class. The communicator could serialize the objects down to XML, send the XML across to the 3rd party system, read the XML response back from the 3rd party, and deserialize back into objects. That would simplify the process of sending requests and allow for additional business methods to be added at will.

Future Columns

The next column has yet to be determined. If you have something in particular that you would like to see explained, please e-mail me at mstrawmyer@crowechizek.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

Most Popular Programming Stories

More for Developers

RSS Feeds