15.1 Creating a Stream-Based Server

 <  Day Day Up  >  

You want to create a server application that connects and communicates with client applications using a defined protocol.


Technique

A stream-based server is one in which the connection between a client and server remains open until you close the socket itself. The data at both endpoints travels using a stream-based method that is similar to the methodology employed by the various file-based streaming methods .

Creating a stream-based server generally involves two main steps, listening for incoming connections and communicating with connected clients . To listen for incoming connections, use the TcpListener class. The constructor for this class uses a IPAddress object for the first parameter and an integer value specifying the port number you want to listen on. You can, however, combine the two parameters into a single IPEndPoint object. A computer might have several network interfaces, so to simplify the process of finding the most appropriate interface, you can pass IPAddress.Any as the first parameter. For the port number, the normal range of values is between 1024 and 5000 because others are standardized to specific protocols. Therefore, to create a TcpListener object on port 2003 using a default network interface, the code would appear like the following:

 
 TcpListener listener = new TcpListener( IPAddress.Any, 2003 ); 

Once you create the listener, you are ready to begin listening for incoming connections. This is accomplished by calling the Start method defined in the TcpListener class. Your application can then do one of two things. If you are not worried about making a blocking call to a function, then call the AcceptSocket method. This method does not return until a client connects to your server. For console applications, this choice is generally fine, but for Windows Forms applications, you run the risk of starving your user -interface thread. To prevent this problem, you should periodically, through either a Timer or a Thread object, call the Pending method defined in the TcpListener class. This method returns a Boolean value denoting whether a client is attempting a connection. If this value is true , then you can call the AcceptSocket method mentioned earlier. Listing 15.1 demonstrates the process of creating a TcpListener object that listens on port 2003.

Listing 15.1 Creating a TcpListener
 static void Main(string[] args) {     Random rand = new Random( DateTime.Now.Millisecond );     int curRandom;     try     {         // listen on port 2003         TcpListener tcpl = new TcpListener(IPAddress.Any, 2003);         tcpl.Start();         Console.WriteLine("Number Guess Server Started");         while (true)         {             string cmd = "";             bool bCloseConnection = false;             // Accept will block until someone connects             Socket s = tcpl.AcceptSocket(); 

It is at this point where a connection is made to a client and communication can begin. It is also at this point where a well-defined protocol comes into being. The data being sent and received is pointless without some sort of contract between the client and server specifying how to interpret the data. In keeping with the standard numerical code followed by the descriptive message scheme used in many protocols, this protocol uses three sets of numbers . The 300-level messages are commands sent from the client to the server. The 400-level messages are sent from the server to the client, and the number 200 is a generic message for sending an OK status, which appears in the following code:

 
 Console.WriteLine( "Incoming client connected: IP={0}", s.RemoteEndPoint.ToString() ); // send the OK status message SendData( s, "200\r\nOK\r\n\r\n" ); // generate new random number curRandom = rand.Next( 1, 100 ); 

In the main body of the do / while loop in Listing 15.2, after the connection is made, the server receives data by calling the helper method ReceiveData and then parses the received message to determine the action to take based on what the client has sent.

Listing 15.2 Establishing a Defined Protocol
 do {     cmd = ReceiveData( s );     // determine which action to take based on response     switch( GetResponseCode( cmd ))     {         case( 300 ):         {             // client wants instructions on how to play             SendData( s, "200\r\nPick a number " +                "between 1 and 100 and the server will " +                "return if it's too high, too low or " +                "correct\r\n\r\n" );             break;         }         case( 301 ):         {             // client is guessing number             try             {                 int guess = Int32.Parse(                     GetResponseData( cmd ));                 if( guess < curRandom )                 {                     SendData( s,                       "401\r\nNumber is too low\r\n\r\n");                 }                 else if( guess > curRandom )                 {                     SendData( s,                       "401\r\nNumber is too high\r\n\r\n");                 }                 else                 {                     SendData( s, "400\r\nCorrect! The " +                         "number is " + curRandom +                         "\r\n\r\n" );                     bCloseConnection = true;                 }             }             catch             {                SendData(s,"404\r\nInvalid guess!\r\n\r\n");             }             break;         }         case( 302 ):         {             // client is quitting             bCloseConnection = true;             break;         }         default:         {             break;         }     } } while( GetResponseCode( cmd ) !=       302 && bCloseConnection == false ); 

In the ReceiveData method, a do / while loop reads incoming data from the client using a packet size of 1024 bytes. Due to the nature of this application, 1KB is a liberal packet size because the data being received will never be that large. The point is that as you develop your application, you might want to experiment with that number to see what value gives optimal performance. Additionally, the Receive method expects a byte array to place data in, which means you might have to convert it as is the case with the GetString method defined in the Encoding.ASCII class. Listing 15.3 shows how to read incoming data from a client and also contains the methods used to parse the incoming data to extract the protocol codes.

Listing 15.3 Receiving Client Data
 public static string ReceiveData( Socket client ) {     string data = "";     int bytesRecvd = 0;     int totalBytes = 0;     byte[] recvData = new byte[1024];   // 1k packet size     do     {         bytesRecvd = client.Receive( recvData, 0,             1024, SocketFlags.None );         if( bytesRecvd > 0 )         {             data += Encoding.ASCII.GetString( recvData );             totalBytes += bytesRecvd;         }     } while( bytesRecvd == 1024 );     return data; } // methods to break apart protocol messages // extracts the numerical identifier at beginning of message public static int GetResponseCode( string response ) {     string code = response.Substring( 0, response.IndexOf( '\r' ));     return Int32.Parse( code ); } // extracts extra message data public static string GetResponseData( string response ) {     return response.Substring( response.IndexOf('\n')+1,         response.LastIndexOf("\r\n\r\n")-response.IndexOf('\n')+1 ); } 

In Listing 15.2, once a valid command is received, an appropriate return message is sent with corresponding data using the SendData method. The act of sending the data is a lot less involved than receiving because you are able to send the entire buffer without worrying about chunking data. Recipe 15.6, "Creating a Connectionless UDP-Based Client," creates a client that interacts with this server:

 
 public static void SendData( Socket client, string response ) {     // Convert the string to a Byte Array and send it     Byte[] data = Encoding.ASCII.GetBytes(response.ToCharArray());     client.Send(data, data.Length, SocketFlags.None); } 

Comments

A majority of Internet technologies in use today utilize the same methods shown in this section. Specifically, the Transmission Control Protocol (TCP) is the underlying protocol that enforces how the transfer of data flows from one remote computer to another. A server and client using TCP remain in a connected state throughout the duration of the session as data is streamed between the two.

The listings in this section show how to implement a simple server that uses TCP to connect to and communicate with remote computers. As you read the code, you'll see the steps outlined earlier. The server first creates a new TcpListener object that sits and waits for incoming connections. Once a connection is detected , which occurs whenever the blocking method AcceptSocket returns, the main server/client communication loop begins. This communication uses the Socket class to both receive and send data to the client computer. The steps and type of data that is sent and received is termed the protocol , and it is necessary to ensure data integrity and consistent communication between server and client.

One thing to note is the number of, or lack of, simultaneous connections that this server can handle. Once the AcceptSocket method returns and the communication loop begins, the server is unable to service any other connection attempts made by any other client. This limit means that only one client can use the server's information at one time, leaving a large queue of other clients waiting for their turn . Solving this problem requires the use of threading . Threading is discussed in detail in Chapter 22, "Threading and Synchronization." Any time a blocking method is called, it should signal to you that your application might need threading. Threading is especially important when using Windows Forms applications so the user-interface thread can continue uninterrupted and with networking applications where the potential for multiple simultaneous connections exists.

To make the server in Listing 15.1 able to support multiple connections, move the communication loop into the thread procedure. Create a new Thread object that can utilize the created Socket object representing the new client object after the AcceptSocket method returns. After the new thread starts, the server can then call the AcceptSocket method again and wait for another connection while currently connected clients are being serviced within the thread.

 <  Day Day Up  >  


Microsoft Visual C# .Net 2003
Microsoft Visual C *. NET 2003 development skills Daquan
ISBN: 7508427505
EAN: 2147483647
Year: 2003
Pages: 440

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net