Architecture & Design Objects and Client/Server Connections
Objects and Client/Server
Connections
By Matt Weisfeld May 8, 2006
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
× we
unexpected occurs. We will first review the client/server application that
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(111
while (true) {
System.out.println("Server Waiting");
Socket pipe = socketConnection.accept();
ObjectInputStream serverInputStream = new
ObjectInputStream(pipe.getInputS
ObjectOutputStream serverOutputStream =
ObjectOutputStream(pipe.getOutpu
employee = (Employee )serverInputStream.readObje
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.readObje
System.out.println("Serving " + employee.getEmpl
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 incompatibl
esc serialVersionUID = -2555159439224478262, local class serialV
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.