http://www.developer.com/net/net/article.php/2232391/Multithreading-in-NET-Applications-Part-3.htm
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. 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: 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: 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. The following sample code contains a client application that will connect to the listener and display the results to the console window. 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. 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. 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. 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. 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: 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. 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. # # #
Multithreading in .NET Applications, Part 3
July 8, 2003
Network Programming Basics
Listener Application
Sample Listener Code Listing
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
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


Multithreaded Listener Application
Sample Listener Code Listing
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
Possible Enhancements
Future Columns
About the Author