The TCP Client/Server

Introduction

Some of the general characteristics of clients and server applications are, in general, client applications have the following characteristics:

  • They are applications that become a client temporarily when remote acces is needed, but perform other computation locally.

  • They are invoked by a user and execute for one session.

  • They run locally on the user's local computer.

  • They actively initiate contact with a server and so must know the server details.

  • They can access multiple services as required.

In general, server applications have the following characteristics:

  • They are special-purpose applications dedicated to providing one service.

  • They are invoked automatically when a system boots, and continue to execute through many sessions.

  • They generally run on a shared computer.

  • They wait passively for contact from remote clients.

  • They accept contact from clients, but offer a single service.

Note that the word server here is referring to a piece of software. However, a computer running one or more server applications is often also referred to as a server.

We will now write a client/server application pair where:

  • The Client will take some data from the user and validate it (as in Finger)

  • The Client will then send this data to the Server for processing (as in Finger)

  • The Server will process the data and return the results

  • The Client will receive the results and display the results in some form.

For our example here We will do this by building a Date/Time Service. The client will simply call the server and ask it for the current date and time.

The Date Client/Server Overview

The Date Server is possibly the most complex part of this application, in this case consisting of three main classes:

  • The DateTimeService class - A basic class to get the current date and time when it is called

  • The HandlerConnection class - This class is used to handle any requests that arrive to the DateServer. This class is specially designed to allow a later transition to a threaded server.

  • The DateServer class - This is the main server application, that simply listens for connections and creates a HandlerConnection object when a connection occurs to the server

We will choose the port 5050 as the port to which we send and listen to for our service. Both a client and a server need to create their own sockets. On the server we listen to port 5050.

The Date Client is very similar to the Finger client we looked at in the section called “The Finger Client” except that the data transfer uses an Object Stream to send and receive data - we will discuss this later.

We will first discuss what occurs in the client/server application before looking directly at the code. First off, the server is started by typing the command java DateServer on the physical server machine. The DateServer starts and begins listening for connections on port 5050. In Figure 10.6, “The Date Client Creates a Connection to the Date Server.” the DateClient class is executed by using the command java DateClient theServer where theServer is the hostname of the physical machine on which the DateServer class is located. The DateClient creates a socket and connects to the DateServer server socket running at port 5050. The DateServer should accept this connection provided it is not busy.

Figure 10.6. The Date Client Creates a Connection to the Date Server.

The Date Client Creates a Connection to the Date Server.

Next, as in Figure 10.7, “The Date Server creates a HandleConnection object.” the DateServer class creates a HandleConnection object to which it passes a reference to the DateClient's socket details. The HandleConnection class then creates an instance of the DateTimeService. It also establishes an input/output stream to the DateClient.

Figure 10.7. The Date Server creates a HandleConnection object.

The Date Server creates a HandleConnection object.

Next, as in Figure 10.8, “The Date Client passes the command to the HandleConnection.” the DateClient passes a command to the HandleConnection. In this case the command is a String object that contains the text "GetDate". When the HandleConnection receives the String object it compares it to see if it is a valid command. In our case the only valid command is "GetDate" so if it is this command then the DateTimeService is called to request the date/time. At this time, since we have not threaded our server application the DateServer is not actually listening to port 5050, rather it is waiting for a response from the HandleConnection object.

Figure 10.8. The Date Client passes the command to the HandleConnection.

The Date Client passes the command to the HandleConnection.

in Figure 10.9, “The HandleConnection gets the Date/Time and sends it to the Client.” the DateTimeService sends the data/time details back to the HandleConnection object where it is then passed on to the DateClient as a String object such as "Mon 15th...". The DateClient will then display the returned data on the client's machine.

Figure 10.9. The HandleConnection gets the Date/Time and sends it to the Client.

The HandleConnection gets the Date/Time and sends it to the Client.

Finally as in Figure 10.10, “The HandleConnection shuts down. The DateServer starts listening again.” the HandleConnection object shuts down connection and is destroyed. Control is returned to the DateServer class and the server once again begins listening to port 5050. The client is now disconnected after having received the data. It now shuts down in our case. The next client is now free to connect to port 5050 on the DateServer and everything happens again. The DateServer will listen forever unless we shut it down by typing CTRL-C in the terminal window.

Figure 10.10. The HandleConnection shuts down. The DateServer starts listening again.

The HandleConnection shuts down. The DateServer starts listening again.

The Running Server/Client Applications

Here you can see the client and server applications working as if you executed them from the command prompt. The DateServer is executed by typing java DateServer. There is no need to specify a port number as 5050 is hard coded and there is no need to specify a host name as the code will use the current machine as the server. Figure 10.11, “The DateServer running on "localhost"” shows the DateServer being executed and it stating that it has "started listening on port 5050". The server then accepts 3 different connections from three clients that request the time. You will notice that there is 10 seconds between each call.

Figure 10.11. The DateServer running on "localhost"

The DateServer running on "localhost"

Figure 10.12, “The Date Client specifing a server at "localhost"” displays the DateClient running four times. The first time that the client was run the server name was not specified, so the error was displayed. The next time the client was executed correctly using java DateClient localhost where the server is specified as being on the same machine (we could also have typed 127.0.0.1 instead of localhost). The client connects to the server and received that the date is Wed Jul 16 15:13:18 BST 2003. The client disconnected and shut down. The client was executed two further times at 10 second intervals.

Figure 10.12. The Date Client specifing a server at "localhost"

The Date Client specifing a server at "localhost"

The Source Code

There are four classes required for this example, as listed below. Download these classes into the one directory - e.g. c:\temp on your machine and compile them by typing javac *.java. Once they have compiled successfully then open a second DOS/Unix command prompt/terminal and execute the server in one by typing java DateServer and execute the client in the second command prompt/server by typing java DateClient localhost where both applications are on the one machine. If you want to run the client on another PC simply execute it using java DateClient the.server.name where the.server.name is the IP/DNS name of the machine on which the server is running.

Here is the source code for the DateTimeService class that provides the date and time to the HandleConnection class - as below and as in DateTimeService.java

 1 
 2   
 3 
 4  // The DateTimeService class that provides the current date
 5  // by Derek Molloy.
 6 
 7  import java.util.*;
 8 
 9  public class DateTimeService
10  {
11    private Date theDate;
12 
13    //constructor gets the current date/time
14 
15    public DateTimeService()
16    {
17      this.theDate = new Date();
18    }
19 
20    //method returns date/time as a formatted string
21 
22    public String getDateAndTime()
23    {
24      return "The date is:" + this.theDate.toString();	
25    }	
26  }
27   
28 

Here is the source code for the DateServer class that runs the server application and creates an instance of the HandleConnection whenever a connection is made by a client - as below and as in DateServer.java

 1 
 2 
 3 
 4 // The DateServer - The server application that passes control 
 5 // to the Handle Connection -  by Derek Molloy
 6 
 7 import java.net.*;
 8 import java.io.*;
 9 
10 public class DateServer 
11 {
12     public static void main(String args[]) 
13     {
14         ServerSocket serverSocket = null;
15         try 
16         {
17             serverSocket = new ServerSocket(5050);
18             System.out.println("Server has started listening on port 5050");
19         } 
20         catch (IOException e) 
21         {
22             System.out.println("Error: Cannot listen on port 5050: " + e);
23             System.exit(1);
24         }
25         while (true) // infinite loop - loops once for each client
26         {
27             Socket clientSocket = null;
28             try 
29             {
30             	//waits here (forever) until a client connects
31                 clientSocket = serverSocket.accept(); 
32                 System.out.println("Server has just accepted socket 
33                                     connection from a client");
34             } 
35             catch (IOException e) 
36             {
37                 System.out.println("Accept failed: 5050 " + e);
38                 break;
39             }	
40 
41             // Create the Handle Connection object - only create it
42             HandleConnection con = new HandleConnection(clientSocket);
43 
44             if (con == null) //If it failed send and error message
45             {
46                 try 
47                 {
48                     ObjectOutputStream os = new ObjectOutputStream(
49                                           clientSocket.getOutputStream());
50                     os.writeObject("error: Cannot open socket thread");
51                     os.flush();
52                     os.close();
53                 } 
54                 catch (Exception ex)  //failed to even send an error message
55                 {
56                     System.out.println("Cannot send error back 
57                                           to client:  5050 " + ex);
58                 }
59             }
60             else { con.init(); } // otherwise we have not failed to 
61                                  // create the HandleConnection object
62 				 // run it now.
63         }
64         try  // do not get here at the moment 
65         {
66             System.out.println("Closing server socket.");
67             serverSocket.close();
68         } 
69         catch (IOException e) 
70         {
71             System.err.println("Could not close 
72                                 server socket. " + e.getMessage());
73         }
74     }
75 }
76    
77 

Here is the source code for the HandleConnection class that is created by the DateServer class whenever a connection is received. This class is responsible for dealing with each of the client requests. HandleConnection.java

  1 
  2   
  3 // The Handle connection class - that deals will all client requests 
  4 // directly - by Derek Molloy
  5 
  6 import java.net.*;
  7 import java.io.*;
  8 import java.util.*;
  9 
 10 
 11 public class HandleConnection
 12 {
 13 
 14     private Socket clientSocket;      // Client socket object
 15     private ObjectInputStream is;     // Input stream
 16     private ObjectOutputStream os;    // Output stream
 17     private DateTimeService theDateService;
 18     
 19     // The constructor for the connecton handler
 20 
 21     public HandleConnection(Socket clientSocket) 
 22     {
 23         this.clientSocket = clientSocket;
 24 
 25         //Set up a service object to get the current date and time
 26         theDateService = new DateTimeService();
 27     }
 28 
 29     // The main execution method 
 30     public void init() 
 31     {
 32         String inputLine;
 33 
 34         try 
 35         {
 36             this.is = new ObjectInputStream(clientSocket.getInputStream());
 37             this.os = new ObjectOutputStream(clientSocket.getOutputStream());
 38             while (this.readCommand()) {}
 39          } 
 40          catch (IOException e) 
 41          {
 42                 e.printStackTrace();
 43          }
 44     }
 45 
 46     // Receive and process incoming command from client socket 
 47 
 48     private boolean readCommand() 
 49     {
 50         String s = null;
 51 
 52         try 
 53         {
 54             s = (String)is.readObject();
 55         } 
 56         catch (Exception e) 
 57         {
 58             s = null;
 59         }
 60         if (s == null) 
 61         {
 62             this.closeSocket();
 63             return false;
 64         }
 65 
 66         // invoke the appropriate function based on the command 
 67         if (s.equals("GetDate")) 
 68         { 
 69             this.getDate(); 
 70         }       
 71         else 
 72         { 
 73             this.sendError("Invalid command: " + s); 
 74         }
 75         return true;
 76     }
 77 
 78     private void getDate()	// uses the date service to get the date
 79     {        
 80         String currentDateTime = theDateService.getDateAndTime();
 81         this.send(currentDateTime);
 82     }
 83 
 84 
 85     // Send a message back through to the client socket as an Object
 86     private void send(Object o) 
 87     {
 88         try 
 89         {
 90             System.out.println("Sending " + o);
 91             this.os.writeObject(o);
 92             this.os.flush();
 93         } 
 94         catch (Exception ex) 
 95         {
 96             ex.printStackTrace();
 97         }
 98     }
 99     
100     // Send a pre-formatted error message to the client 
101     public void sendError(String msg) 
102     {
103         this.send("error:" + msg);	//remember a string IS-A object!
104     }
105     
106     // Close the client socket 
107     public void closeSocket()		//close the socket connection
108     {
109         try 
110         {
111             this.os.close();
112             this.is.close();
113             this.clientSocket.close();
114         } 
115         catch (Exception ex) 
116         {
117             System.err.println(ex.toString());
118         }
119     }
120  }
121 
122   
123 

Here is the source code for the DateClient class that is the only class required on the client side. It connects to the server and requests the date/time. DateClient.java

  1 
  2   
  3 
  4 // The Date Client - The date client connects to the server, sends 
  5 // the "GetDate" command and waits for a response - by Derek Molloy
  6 
  7 import java.net.*;
  8 import java.io.*;
  9 import java.util.*;
 10 
 11 public class DateClient 
 12 {
 13 
 14     private Socket socket = null;
 15     private ObjectOutputStream os = null;
 16     private ObjectInputStream is = null;
 17 
 18     // the constructor expects the IP address of the server - the 
 19     // port is fixed at 5050
 20     public DateClient(String serverIP) 
 21     {
 22 	if (!connectToServer(serverIP)) 
 23         {
 24 	   System.out.println("Cannot open socket connection...");            
 25 	}
 26     }
 27 
 28     private boolean connectToServer(String serverIP) 
 29     {
 30 	try  // open a new socket to port: 5050 and create streams
 31         {
 32 	   this.socket = new Socket(serverIP,5050);
 33 	   this.os = new ObjectOutputStream(this.socket.getOutputStream());
 34 	   this.is = new ObjectInputStream(this.socket.getInputStream());
 35 	   System.out.print("Connected to Server\n");
 36 	} 
 37         catch (Exception ex) 
 38         {
 39 	  System.out.print("Failed to Connect to Server\n" + ex.toString());	
 40 	  System.out.println(ex.toString());
 41 	  return false;
 42 	}
 43 	return true;
 44     }
 45 
 46     private void getDate() 
 47     {
 48        String theDate;
 49        System.out.println("GetDate");
 50        this.send("GetDate");
 51        theDate = (String)receive();
 52        if (theDate != null)
 53        {
 54 	  System.out.println("The Server's date is " + theDate);
 55        }
 56     }
 57 	
 58     // method to send a generic object.
 59     private void send(Object o) 
 60     {
 61 	try 
 62         {
 63 	   System.out.println("Sending " + o);
 64 	   os.writeObject(o);
 65 	   os.flush();
 66 	} 
 67         catch (Exception ex) 
 68         {
 69 	   System.out.println(ex.toString());
 70 	}
 71     }
 72 
 73     // method to receive a generic object.
 74     private Object receive() 
 75     {
 76 	Object o = null;
 77 	try 
 78         {
 79 	   o = is.readObject();
 80 	} 
 81         catch (Exception ex) 
 82         {
 83 	   System.out.println(ex.toString());
 84 	}
 85 	return o;
 86     }
 87 
 88     public static void main(String args[]) 
 89     {
 90 	if(args.length>0)
 91 	{
 92 	   DateClient theApp = new DateClient(args[0]);
 93 	   try
 94            {
 95               theApp.getDate();
 96  	   } 
 97            catch (Exception ex)
 98            {
 99 	      System.out.println(ex.toString());
100 	   }
101 	}
102 	else
103 	{
104 	   System.out.println("Error: you must provide the IP of the server");
105 	   System.exit(1);
106 	}
107 	System.exit(0);
108     }
109  }
110 
111   
112