Multithreading in .NET Applications, Part 3

Mark Strawmyer Presents: .NET Nuts & Bolts


Multithreading is a powerful design tool for creating high-performance applications, especially those that require user interaction. Microsoft .NET has broken down the barriers that once existed in creating multithreaded applications. The last two installments of the .NET Nuts & Bolts column were Part 1 and Part 2 of the exploration of multithreading with the .NET Framework. In the first article (Part 1), we covered the background of threading, benefits of threading, and provided a demonstration. In the second article (Part 2), we looked at the basic methods involved with working with threads and the synchronization of thread activity. In this article, the third and final of the series on multithreading, we will look at how threads can be used to write a server application to accept multiple requests. This will involve using classes from the System.Threading namespace along with classes from the System.Net namespace.

Network Programming Basics

In order for us to write an application that accepts network requests, we must first have a basic understanding of the network components and terminology involved. I will not attempt to provide a full explanation on networking and how it all works; rather, I'll provide the information essential to understanding this topic. Some basic definitions are as follows:

  • TCP/IP—A communications protocol suite used by computers to communicate across a network. It is a routable protocol, which means that a router will forward the communications to the appropriate location if the destination does not rely on its network.
  • Port—Each TCP/IP based application program has unique port numbers assigned to it. The port number identifies the logical communications channel that is to be used to connect to the application. Some protocols use a well-known port reserved specifically for their use. For example, if you want to connect to a Web server, you typically use port 80, which is reserved for HTTP.
  • Socket—A socket is one endpoint of a two-way network connection between two programs. It is a mechanism for communication across processes on the same machine or on different computers connected by a network. A socket is connected to a specific port to communicate with the desired application.

Listener Application

Listener applications, also known as server applications, open a network port and then wait for clients to connect and make requests. Examples of such applications include but are not limited to Web servers, database servers, e-mail servers, and chat servers. Listener applications generally follow a similar algorithm. The algorithm is as follows:

  • Open available port
  • Wait to accept client socket connection through port
  • Client connects through socket and either makes a request or the connection alone serves as the sole request
  • Listener performs some process and sends a response
  • Close the socket connection to the client

Sample Listener Code Listing

The following sample follows the basic algorithm above. It contains a console application that opens a port and waits for a socket connection to be made. Rather than have the client make any type of formal request for action, we'll simply use the connection as the request. Once a client is connected, the server sends 10 date/time messages to the client with a pause between each message. The listener closes the connection and begins to wait for another client connection.

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace CodeGuru.MultithreadedPart3
{
  /// <remarks>
  /// Example console application demonstrating a listener/server
  /// application.
  /// Waits for connections to be made and responds with a message.
  /// </remarks>
  class HelloWorldServer
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      try
      {
        DateTime now;
        String dateStr;

        // Choose a port other than 8080 if you have difficulty
        TcpListener listener = new TcpListener(IPAddress.Loopback,
                                               8080);
        listener.Start();

        Console.WriteLine("Waiting for clients to connect");
        Console.WriteLine("Press Ctrl+c to Quit...");
        while(true)
        {
          // Accept blocks until a client connects
          Socket clientSocket = listener.AcceptSocket();

          for( int i = 0; i < 10; i++ )
          {
            // Get the current date and time then build a
            // Byte Array to send
            now = DateTime.Now;
            dateStr = now.ToShortDateString() + " "
                    + now.ToLongTimeString();
            Byte[] byteDateLine = Encoding.ASCII.GetBytes(
                                  dateStr.ToCharArray());

            // Send the data
            clientSocket.Send(byteDateLine, byteDateLine.Length, 0);
            Thread.Sleep(1000);
            Console.WriteLine("Sent {0}", dateStr);
          }
          clientSocket.Close();
        }
    }
    catch( SocketException socketEx )
      {
        Console.WriteLine("Socket error: {0}", socketEx.Message);
      }
    }
  }
}

Sample Client Code Listing

The following sample code contains a client application that will connect to the listener and display the results to the console window.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace CodeGuru.MultithreadedPart3
{
  /// <remarks>
  /// Example console application demonstrating a client
  /// application.
  /// Makes a connection to the server and displays the response.
  /// </remarks>
  class HelloWorldClient
  {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      bool isDone = false;
      Byte[] read = new Byte[32];
      TcpClient client = new TcpClient("localhost", 8080);

      // Get the stream to read the input
      Stream s;
      try
      {
        s = client.GetStream();
      }
      catch( InvalidOperationException )
      {
        Console.WriteLine("Cannot connect to localhost");
        return;
      }

      // Read the stream and convert it to ASII
      while( !isDone )
      {
        int numBytes = s.Read(read, 0, read.Length);
        String data = Encoding.ASCII.GetString(read);
        if( numBytes == 0 )
        {
          isDone = true;
        }
        else
        {
          Console.WriteLine("Received {0} bytes: {1}",
                             numBytes, data);
        }
      }
      client.Close();
    }
  }
}

Testing the Listener Using the Client

Copy the listener and client samples into separate solutions and compile each. Open a command line and run the listener application. Open another command line and run the client application. You will see output similar to the following:

Figure 1—Listener

Figure 2—Client

Start the execution of the client application again. From a third command line, execute another instance of the client application. You should see that only one of them is receiving a response from the server. The other client is sitting, waiting for the server to respond. Once the first client completes, the second client will then receive response from the server.

Multithreaded Listener Application

The problem with the previous example is that the server will only process one connection at a time. That may be sufficient for our simple date/time example, but for other listener applications, such as Web servers, that won't do. The reason that only one client is processed at a time is because our listener application is single threaded and therefore only has a single unit of execution. To process multiple client requests simultaneously, we would need to handle each client on a different thread.

Sample Listener Code Listing

The following sample code changes the previous listener application so that it can process multiple client requests at a time. This is handled by moving the processing into a separate method that is called on a new thread for each client connection. Each client socket is assigned to a new instance of the HelloWorldServer so that it can be passed to the new thread.

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace CodeGuru.MultithreadedPart3
{
  /// <remarks>
  /// Example console application demonstrating a listener/server
  /// application.
  /// Waits for connections to be made and responds with a message.
  /// </remarks>
  class HelloWorldServer
  {
    // Socket to use to accept client connections
    private Socket _socket;

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
      try
      {
        TcpListener listener = new TcpListener(
                                   IPAddress.Loopback, 8080);
        listener.Start();

        Console.WriteLine("Waiting for clients to connect");
        Console.WriteLine("Press Ctrl+c to Quit...");
        while(true)
        {
          // Accept blocks until a client connects
          HelloWorldServer hwServer = new HelloWorldServer();
          hwServer._socket = listener.AcceptSocket();

          // Process the client connection on a new thread
          Thread sampleThread = new Thread(new ThreadStart(
                                           hwServer.Process));
          sampleThread.Start();
        }
      }
      catch( SocketException socketEx )
      {
        Console.WriteLine("Socket error: {0}", socketEx.Message);
      }
    }

    /*
     * Get the current date and time and send it to the client.
     * Requires that a socket is created and connected to a client.
     * Closes the socket when complete.
     */
    private void Process()
    {
      DateTime now;
      String dateStr;

      for( int i = 0; i < 10; i++ )
      {
        // Get the current date and time then concatenate build
        // a Byte Array to send
        now = DateTime.Now;
        dateStr = now.ToShortDateString() + " "
                + now.ToLongTimeString();
        Byte[] byteDateLine = Encoding.ASCII.GetBytes(
                                      dateStr.ToCharArray());

        // Send the data
        this._socket.Send(byteDateLine, byteDateLine.Length, 0);
        Thread.Sleep(1000);
        Console.WriteLine("Sent {0}", dateStr);
      }
      this._socket.Close();
    }
  }
}

Testing the Multithreaded Listener Using the Client

Copy the updated listener application code into the appropriate solution and recompile. Run the listener application from a command line. Then run multiple instances of the client application from different command line prompts. You will notice that both clients now receive a response from the server.

Possible Enhancements

The samples given above contain a simple listener and client application. You can use them as a starting point to further explore the System.Net namespace. A couple of possibilities are as follows:

  • Expand the client to send the server a request for a specific action. Modify the server to allow the client to make a request and respond with the appropriate action. For example, the client could be required to send "DATETIME" as a request, and the server would then respond to the client with the date and time.
  • Remove the console output from the listener application. Change it into a Windows Service to allow it to start up automatically and run in the background.

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, you could reach me at mstrawmyer@crowechizek.com.

About the Author

Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large- and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in the architecture, design, and development of Microsoft-based solutions. You can reach Mark 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

  • tools to build protocols

    Posted by Nick_j on 05/14/2010 11:37pm

    well, nice start but it takes ages to build your complete TCP protocol, I found recently good tool to build TCP protocols in www.protocol-builder.com it generates the protocol code for the server connection which can accept many connections from the client, but I like to understand the generated code, thank you.

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

Top White Papers and Webcasts

  • Cisco and Intel have harnessed flash memory technology and truly innovative system software to blast through the boundaries of today's I/O-bound server/storage architectures. See how they are bringing real-time responsiveness to data-intensive applications—for unmatched business advantage. Sponsored by Cisco and Intel® Partnering in Innovation

  • Corporate e-Learning technology has a long and diverse pedigree. As far back as the 1980s, companies were adopting computer-based training to supplement traditional classroom activities. More recently, rich web-based applications have added streaming audio and video, real-time collaboration and other new tools to the e-Learning mix. At the same time, the growing availability of informal learning tools--a category that includes everything from web searches to social media posts--are having a major impact on …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds