Architecture & DesignObjects and Client/Server Connections

Objects and Client/Server Connections

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

This series, The Object-Oriented Thought Process, is intended for someone just learning an object-oriented language and who wants to understand the basic concepts before jumping into the code, or someone who wants to understand the infrastructure behind an object-oriented language he or she is already using. These concepts are part of the foundation that any programmer will need to make the paradigm shift from procedural programming to object-oriented programming.

Click here to start at the beginning of the series.

In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems Web site http://java.sun.com/. You can download the standard edition, J2SE 5.0, at http://java.sun.com/j2se/1.5.0/download.jsp to compile and execute these applications. I often reference the Java J2SE 5.0 API documentation and I recommend that you explore the Java API further. Code listings are provided for all examples in this article as well as figures and output (when appropriate). See the first article in this series for detailed descriptions for compiling and running all the code examples.

In the previous column, we covered the basic Java technology required to construct a simple client/server example. The example illustrated how an object is constructed on one machine and then marshaled across a network to another machine. The process involves serializing the object so that it can be transferred across a wire and then reconstructed on the other side. In this article, we will further explore the process of moving an object across a client-server connection. Specifically, we will investigate what happens when the unexpected occurs. We will first review the client/server application that we began in the last column, and then we will throw in some wrinkles.

Running the Client/Server Example

First, let’s review the code from the last column. The examples in this article extend this code, so it is important that you reach this baseline by compiling and running the simple client server example (if you have not read the previous column or would like to review it, please visit the following URL: http://www.developer.com/db/article.php/3597071).

There are 3 files in this example, Employee.java, Server.java and Client.java.

The basic concept is this:

  • An Employee object is created
  • Attributes in the Employee object are set
  • The client sends Employee object is sent across a network
  • The server accepts the Employee object
  • The server changes the Employee object’s attributes
  • The server sends the Employee object back to the client
  • The client verifies that the attributes were indeed altered

The complete code for the Employee class is shown in Listing 1.

import java.io.*;
import java.util.*;

public class Employee implements Serializable {

   private int employeeNumber;
   private String employeeName;

   Employee(int num, String name) {
      employeeNumber = num;
      employeeName= name;
   }

   public int getEmployeeNumber() {
      return employeeNumber ;
   }

   public void setEmployeeNumber(int num) {
      employeeNumber = num;
   }

   public String getEmployeeName() {
      return employeeName ;
   }

   public void setEmployeeName(String name) {
      employeeName = name;
   }
}

Listing 1: The Employee Object

The complete code for the Client class is shown in Listing 2.

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

public class Client {

   public static void main(String[] arg) {
      try {
         Employee joe = new Employee(150, "Joe");

         System.out.println("employeeNumber= " + joe
                            .getEmployeeNumber());
         System.out.println("employeeName= " + joe
                            .getEmployeeName());

         Socket socketConnection = new Socket("127.0.0.1", 11111);


         ObjectOutputStream clientOutputStream = new 
                        ObjectOutputStream(socketConnection
                                           .getOutputStream());
         ObjectInputStream clientInputStream = new 
         ObjectInputStream(socketConnection.getInputStream());

         clientOutputStream.writeObject(joe);

         joe= (Employee)clientInputStream.readObject();

         System.out.println("employeeNumber= " + joe
                            .getEmployeeNumber());
         System.out.println("employeeName= " + joe
                            .getEmployeeName());

         clientOutputStream.close();
         clientInputStream.close();

      } catch (Exception e) {System.out.println(e); }
   }
}

Listing 2: The Client

The complete code for the Server class is shown in Listing 3.

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

public class Server {

   public static void main(String[] arg) {

      Employee employee = null;

      try {

         ServerSocket socketConnection = new ServerSocket(11111);

         System.out.println("Server Waiting");

         Socket pipe = socketConnection.accept();

         ObjectInputStream serverInputStream = new    
            ObjectInputStream(pipe.getInputStream());

         ObjectOutputStream serverOutputStream = new 
            ObjectOutputStream(pipe.getOutputStream());

          employee = (Employee )serverInputStream.readObject();

         employee .setEmployeeNumber(256);
         employee .setEmployeeName("John");

         serverOutputStream.writeObject(employee);

         serverInputStream.close();
         serverOutputStream.close();


      }  catch(Exception e) {System.out.println(e); 
      }
   }

}

Listing 3: The Server

Compiling and Running the System

Compiling the Code

I compiled the code using a DOS Shell. On certain machines it is called a DOS Shell and on others it is called a Command Prompt. They and equivalent and I will show examples of both. Eventually we will need two of these DOS Shells, one to run the Server and one for the Client. You can open a DOS Shell in the Programs->Accessories option.

Type the following code at the command prompt to compile all three of the files.

"C:Program FilesJavajdk1.5.0_06binjavac" Employee.java
"C:Program FilesJavajdk1.5.0_06binjavac" Client.java
"C:Program FilesJavajdk1.5.0_06binjavac" Server.java

Figure 1 shows the screen shot of how this is accomplished.

Figure 1. Compiling the Code

Starting the Server

In one of the DOS Shells, type in the following line at the command prompt:

"C:Program FilesJavajdk1.5.0_06binjava" Server

Figure 2 shows what happens in the DOS Shell.

Figure 2 – Starting the Server

If everything is working properly, the “Server Waiting” message is displayed. At this point, we can start the Client.

Starting the Client

In a separate DOS Shell, start the Client with the following line.

"C:Program FilesJavajdk1.5.0_06binjava" Client

The result is shown in Figure 3.

Figure 3 – Starting the Client

If all is well, you will see that the employeeNumber and the employeeName both were changed. You can put some specific identification in the print statements to provide further assurance.

With the circuit complete, the Server should exit cleanly, as shown in Figure 4.

Figure 4 – Completing the System

Resetting the Server

What we have created is a very simple client/server application using the Java programming language. At this point, our server accepts a single transaction from the client and then simply terminates. While this is fine for demonstration purposes, it is not very useful. One of the more interesting exercises that I use in the classroom has multiple clients connecting to the same server. For this to occur, the server obviously must not terminate after serving a single transaction.

Handling Multiple Clients

At a very basic level, there is a simple code enhancement that will allow the Server to handle multiple clients. In fact, the server can also handle multiple transactions from the same client. For example, by adding a simple while loop, we can provide this behavior. Listing 4 contains this enhancement.

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

public class Server {

   public static void main(String[] arg) {

         Employee employee = null;

         try {

            ServerSocket socketConnection = new ServerSocket(11111);

	while (true) {

            	System.out.println("Server Waiting");

            	Socket pipe = socketConnection.accept();

            	ObjectInputStream serverInputStream = new    
                 		ObjectInputStream(pipe.getInputStream());

           	 	ObjectOutputStream serverOutputStream = new 
                 		ObjectOutputStream(pipe.getOutputStream());

            	employee = (Employee )serverInputStream.readObject();

            	employee .setEmployeeNumber(256);
            	employee .setEmployeeName("John");
		
            	serverOutputStream.writeObject(employee);

            	serverInputStream.close();
            	serverOutputStream.close();

	} // end of the while loop

         }  catch(Exception e) {System.out.println(e); 
         }
   }

}

Listing 4: Resetting the Server

The two lines in bold represent the code required to implement the loop. Note that several lines of the code must reside within the loop.

Note: In a later article we will explore some of the performance issues that we can address in a client/server environment.

The basic concept here is that once the Server handles a single client transaction, it closes the input/output stream and then loops back to wait for the next client transaction. Each time the following line is encountered, the Server simply waits.

            	Socket pipe = socketConnection.accept();

If no client is sending transactions, the Server blocks and waits to be notified.

The Sun documentation states the following regarding the ServerSocket accept method:

Listens for a connection to be made to this socket and accepts it. The method blocks until a connection is made.
A new Socket s is created and, if there is a security manager, the security manager’s checkAccept method is called with s.getInetAddress().getHostAddress() and s.getPort() as its arguments to ensure the operation is allowed. This could result in a SecurityException.

To test the use of multiple clients, simply create more than one client. Make sure that each client uses the IP address of the server and you are ready to test. Figure 5 shows how my system looks when I am running the Server with 2 different clients.

Figure 5 – Multiple Clients

Identifying the Client

One very useful piece of information that the Server can collect is some sort of client ID. We can actually embed this in the Employee object itself. This ID can take the form of a simple ID number or we can inspect any attribute on the object. In this example, rather than use the employeeNumber attribute, let’s use the employeeName attribute. This will make it easier for us to read and identify the client more easily.

We have already created an Employee object with the employeeName name of Joe, so let’s create one with the employeeName of Mary. We can use the following line of code in the 2nd client application.

        	 Employee mary = new Employee(250, "Mary");

On the server side, we can add a line of code to print out the name of the specific Employee object.

         	employee = (Employee )serverInputStream.readObject();

                System.out.println("Serving " + employee.getEmployeeName());

Figure 6 shows what happens when we use two clients. The first Client has the name of Joe and the second the name of Mary. You can see the specific information printed in the client Command Prompt. The Server Command Prompt is more interesting. Note that the Server handles the message from both Joe and Mary and then prints their name. This illustrates that the Server can indeed handle multiple clients. It is important to realize how nicely all of this is packaged into the objects and how the objects are marshaled across the network.

Figure 6 – Identifying Multiple Clients

Using another Machine on the Network

At this point, rather than using the loopback IP address (“127.0.0.1”), it is interesting to find another computer on the network and connect to the server from a different machine. There will be no change to the client code except for updating the IP address. To find the specific IP address of the machine, you can use the ipconfig command at the Command Prompt of the Server.

         C:column22cs> ipconfig

This command will provide the appropriate IP address for the server. Simply change the loopback string, “127.0.0.1”, to the IP address of the Server.

Keeping things in Sync

Getting this client/server system to work requires that both sides use the same classes. This may seem obvious, but it is easy for the two sides to become out of sync. This has to do with one of the strengths of Java. As is often the case, a specific strength can lead to some potentially significant problems if you are not careful.

For example, let’s make a change to the Client. Let’s say that we want to add a last name to the Employee object as seen in Listing 5.

import java.io.*;
import java.util.*;

public class Employee implements Serializable {

        private String employeeLastName;
        private int employeeNumber;
        private String employeeName;
        
        Employee(int num, String name) {
                employeeNumber = num;
                employeeName= name;
        }
		
        public int getEmployeeNumber() {
                return employeeNumber ;
        }
        
        public void setEmployeeNumber(int num) {
                employeeNumber = num;
        }
		
        public String getEmployeeName() {
                return employeeName ;
        }
        
        public void setEmployeeName(String name) {
                employeeName = name;
        }
        public String setEmployeeLastName() {
                return employeeName ;
        }
        
        public void getEmployeeLastName(String name) {
                employeeName = name;
        }
}

Listing 5: The Employee with lastName added.

Note the lines in bold. These lines add the functionality of the lastName. However, this obviously changes the Employee class. For example, if the Client compiles on one machine with this change and the Server compiles on another with the old version of Employee, a problem will arise (see Figure 7).

Figure 7 – Incompatible Employee Class

The Server reports the following exception:

java.io.InvalidClassException: Employee; local class incompatible: stream classd
esc serialVersionUID = -2555159439224478262, local class serialVersionUID = 1247
525241219380744

The Employee class the Server uses is different than the one that the Client uses – and this is not allowed. The fact that this can even happen is because Java dynamically loads all classes – there is no linked executable. This is a tricky configuration management issue. If you update a class you must make sure that all distributions of that class get updated.

In this example, the Employee class was updated on the Client, but not the Server. Since the Server was expecting a specific Employee class, an exception was generated when it received a different one. The interesting thing to look at is the fact that a class is assigned a serialVersionUID. When these two serialVersionUIDs do no match, a problem is identified. This is as much a security issue as it is a programming issue.

The Sun documentation states the following regarding the serialVersionUID:

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender’s class, then deserialization will result in an InvalidClassException.

Conclusion

In this article, we expanded upon the basic concepts involved with creating a simple Client/Server model. The code is a complete and functional model.

As we have seen, moving an object from one place to another is often a tricky proposition. In languages such as Java and the .Net languages, while the ability to load objects dynamically is a major strength, we have to deal with the problem of keeping the class versions in sync.

While the example of this article is complete and useful, it is quite basic. There are many more fascinating topics to explore regarding client/server applications.

References

  • Tyma, Paul, Gabriel Torok and Troy Downing: Java Primer Plus. The Waite Group, 1996.
  • www.javasoft.com

About the Author


Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, and C# .NET as well as various web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management. Besides The Object-Oriented Thought Process
, which is now in it’s second edition, Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb’s Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories