7.3 Client/Server Socket Programming


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 access 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 ConnectionHandler class - This class is used to handle any requests that arrive to the DateServer. This class is specially designed to allow a further transition to a threaded server in later chapters.

  • The DateServer class - This is the main server application, that simply listens for connections and creates a ConnectionHandler 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 ee402.DateServer on the BeagleBone/Server machine. The DateServer starts and begins listening for connections on server port 5050. In Figure 7.6, “The Date Client Creates a Connection to the Date Server.” the DateClient class is executed by using the command java ee402.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 7.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 7.7, “The Date Server creates a ConnectionHandler object.” the DateServer class creates a ConnectionHandler object to which it passes a reference to the DateClient's socket details. The ConnectionHandler class then creates an instance of the DateTimeService. It also establishes an input/output stream to the DateClient.

Figure 7.7. The Date Server creates a HandleConnection object.

The Date Server creates a HandleConnection object.

Next, as in Figure 7.8, “The Date Client passes the command to the ConnectionHandler .” the DateClient passes a command to the ConnectionHandler . In this case the command is a String object that contains the text "GetDate". When the ConnectionHandler 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 ConnectionHandler object.

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

The Date Client passes the command to the HandleConnection.

in Figure 7.9, “The ConnectionHandler gets the Date/Time and sends it to the Client.” the DateTimeService sends the data/time details back to the ConnectionHandler 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 7.9. The ConnectionHandler 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 7.10, “The ConnectionHandler shuts down. The DateServer starts listening again.” the ConnectionHandler 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 7.10. The ConnectionHandler 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 ee402.DateServer. For the assignment you will do this on the BeagleBone Black. 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 7.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 different connections from the client application.

 Figure 7.11. The DateServer running on "localhost"

Figure 7.12, “The Date Client specifying a server at "localhost"” displays the DateClient running multiple 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 ee402.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). 

Figure 7.12. The Date Client specifying a server at "localhost"

The Source Code

There are four classes required for this example, as listed below. Follow these steps:

  • Download these classes from github using git clone git://github.com/derekmolloy/ee402.git They are in the notes_examples/chapter7 directory.
  • Copy the files from ee402/notes_examples/chapter7 to another directory - e.g. c:\temp\ee402
  • You can use Eclipse, or compile them by typing javac *.java from the c:\temp\ee402 directory. 
  • You can start the server by going back to the c:\temp directory and typing java ee402.Server or it can be run within Eclipse.
  • Open a second DOS/Unix command prompt/terminal, go to the c:\temp directory and execute the client by typing java DateClient localhost (both applications are on the one machine). If you want to run the client on another PC/BeagleBone Black, 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 ConnectionHandler class - as below:

DateTimeService.java

12345678910111213141516171819202122232425262728
/* The Date Time Service Class - Written by Derek Molloy for the EE402 Module
* See: ee402.eeng.dcu.ie
*/

package ee402;

import java.util.Calendar;
import java.util.Date;

public class DateTimeService
{
   private Calendar calendar;

   //constructor creates the Calendar object, could use the constructor:
   // Calendar(TimeZone zone, Locale aLocale) to explicitly specify
   //         the time zone and locale
   public DateTimeService()
   {
         this.calendar = Calendar.getInstance();
   }

   //method returns date/time as a formatted String object
   public String getDateAndTime()
   {
         Date d = this.calendar.getTime();
         return "The BeagleBone time is: " + d.toString();        
   }        
}

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

Server.java

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
/* The Date Server Class - Written by Derek Molloy for the EE402 Module
* See: ee402.eeng.dcu.ie
*/

package ee402;

import java.net.*;
import java.io.*;

public class Server
{
    private static int portNumber = 5050;
        
    public static void main(String args[]) {
                
        boolean listening = true;
        ServerSocket serverSocket = null;
        
        // Set up the Server Socket
        try
        {
            serverSocket = new ServerSocket(portNumber);
            System.out.println("New Server has started listening on port: " + portNumber );
        }
        catch (IOException e)
        {
            System.out.println("Cannot listen on port: " + portNumber + ", Exception: " + e);
            System.exit(1);
        }
        
        // Server is now listening for connections or would not get to this point
        while (listening) // almost infinite loop - loop once for each client request
        {
            Socket clientSocket = null;
            try{
                System.out.println("**. Listening for a connection...");
                clientSocket = serverSocket.accept();
                System.out.println("00. <- Accepted socket connection from a client: ");
                System.out.println(" <- with address: " + clientSocket.getInetAddress().toString());
                System.out.println(" <- and port number: " + clientSocket.getPort());
            }
            catch (IOException e){
                System.out.println("XX. Accept failed: " + portNumber + e);
                listening = false; // end the loop - stop listening for further client requests
            }        
            
            ConnectionHandler con = new ConnectionHandler(clientSocket);
            con.init();
            System.out.println("02. -- Finished communicating with client:" 
+ clientSocket.getInetAddress().toString());
        }
        // Server is no longer listening for client connections - time to shut down.
        try
        {
            System.out.println("04. -- Closing down the server socket gracefully.");
            serverSocket.close();
        }
        catch (IOException e)
        {
            System.err.println("XX. Could not close server socket. " + e.getMessage());
        }
    }
}

Here is the source code for the ConnectionHandler 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:


1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
/* The Connection Handler Class - Written by Derek Molloy for the EE402 Module
* See: ee402.eeng.dcu.ie
*/

package ee402;

import java.net.*;
import java.io.*;

public class ConnectionHandler
{
    private Socket clientSocket = null;                         // Client socket object
    private ObjectInputStream is = null;                        // Input stream
    private ObjectOutputStream os = null;                       // Output stream
    private DateTimeService theDateService;
    
        // The constructor for the connection handler
    public ConnectionHandler(Socket clientSocket) {
        this.clientSocket = clientSocket;
        //Set up a service object to get the current date and time
        theDateService = new DateTimeService();
    }

    // Will eventually be the thread execution method - can't pass the exception back
    public void init() {
         try {
            this.is = new ObjectInputStream(clientSocket.getInputStream());
            this.os = new ObjectOutputStream(clientSocket.getOutputStream());
            while (this.readCommand()) {}
         }
         catch (IOException e)
         {
                System.out.println("XX. There was a problem with the Input/Output Communication:");
            e.printStackTrace();
         }
    }

    // Receive and process incoming string commands from client socket
    private boolean readCommand() {
        String s = null;
        try {
            s = (String) is.readObject();
        }
        catch (Exception e){ // catch a general exception
                this.closeSocket();
            return false;
        }
        System.out.println("01. <- Received a String object from the client (" + s + ").");
        
        // At this point there is a valid String object
        // invoke the appropriate function based on the command
        if (s.equalsIgnoreCase("GetDate")){
            this.getDate();
        }
        else {
            this.sendError("Invalid command: " + s);
        }
        return true;
    }

    // Use our custom DateTimeService Class to get the date and time
    private void getDate() {        // use the date service to get the date
        String currentDateTimeText = theDateService.getDateAndTime();
        this.send(currentDateTimeText);
    }

    // Send a generic object back to the client
    private void send(Object o) {
        try {
            System.out.println("02. -> Sending (" + o +") to the client.");
            this.os.writeObject(o);
            this.os.flush();
        }
        catch (Exception e) {
            System.out.println("XX." + e.getStackTrace());
        }
    }
    
    // Send a pre-formatted error message to the client
    public void sendError(String message) {
        this.send("Error:" + message);        //remember a String IS-A Object!
    }
    
    // Close the client socket
    public void closeSocket() { //gracefully close the socket connection
        try {
            this.os.close();
            this.is.close();
            this.clientSocket.close();
        }
        catch (Exception e) {
            System.out.println("XX. " + e.getStackTrace());
        }
    }
}

Finally the Client application:


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
/* The Client Class - Written by Derek Molloy for the EE402 Module
* See: ee402.eeng.dcu.ie
*
*
*/

package ee402;

import java.net.*;
import java.io.*;

public class Client {
        
        private static int portNumber = 5050;
    private Socket socket = null;
    private ObjectOutputStream os = null;
    private ObjectInputStream is = null;

        // the constructor expects the IP address of the server - the port is fixed
    public Client(String serverIP) {
            if (!connectToServer(serverIP)) {
                    System.out.println("XX. Failed to open socket connection to: " + serverIP);
            }
    }

    private boolean connectToServer(String serverIP) {
            try { // open a new socket to the server
                    this.socket = new Socket(serverIP,portNumber);
                    this.os = new ObjectOutputStream(this.socket.getOutputStream());
                    this.is = new ObjectInputStream(this.socket.getInputStream());
                    System.out.println("00. -> Connected to Server:" + this.socket.getInetAddress()
                                    + " on port: " + this.socket.getPort());
                    System.out.println(" -> from local address: " + this.socket.getLocalAddress()
                                    + " and port: " + this.socket.getLocalPort());
            }
        catch (Exception e) {
                System.out.println("XX. Failed to Connect to the Server at port: " + portNumber);
                System.out.println(" Exception: " + e.toString());        
                return false;
        }
                return true;
    }

    private void getDate() {
            String theDateCommand = "GetDate", theDateAndTime;
            System.out.println("01. -> Sending Command (" + theDateCommand + ") to the server...");
            this.send(theDateCommand);
            try{
                    theDateAndTime = (String) receive();
                    System.out.println("05. <- The Server responded with: ");
                    System.out.println(" <- " + theDateAndTime);
            }
            catch (Exception e){
                    System.out.println("XX. There was an invalid object sent back from the server");
            }
            System.out.println("06. -- Disconnected from Server.");
    }
        
    // method to send a generic object.
    private void send(Object o) {
                try {
                 System.out.println("02. -> Sending an object...");
                 os.writeObject(o);
                 os.flush();
                }
         catch (Exception e) {
                 System.out.println("XX. Exception Occurred on Sending:" + e.toString());
                }
    }

    // method to receive a generic object.
    private Object receive()
    {
                Object o = null;
                try {
                        System.out.println("03. -- About to receive an object...");
                 o = is.readObject();
                 System.out.println("04. <- Object received...");
                }
         catch (Exception e) {
                 System.out.println("XX. Exception Occurred on Receiving:" + e.toString());
                }
                return o;
    }

    public static void main(String args[])
    {
            System.out.println("**. Java Client Application - EE402 OOP Module, DCU");
            if(args.length==1){
                    Client theApp = new Client(args[0]);
                 theApp.getDate();
                }
            else
            {
                    System.out.println("Error: you must provide the address of the server");
                    System.out.println("Usage is: java Client x.x.x.x (e.g. java Client 192.168.7.2)");
                    System.out.println(" or: java Client hostname (e.g. java Client localhost)");
            }
            System.out.println("**. End of Application.");
    }
}


These notes are copyright Dr. Derek Molloy, School of Electronic Engineering, Dublin City University, Ireland 2013-present. Please contact him directly before reproducing any of the content in any way.
Comments