Java Programming, Lesson # 568, Revised 01/30/00.
Students in Prof. Baldwin's Advanced Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.
The lessons in this series have taught you a great deal about the use of sockets in network programming. Several other lessons in this tutorial also deal with network programming, but at a higher level. For example, one series of lessons deals with Java RMI while another series of lessons deals with CORBA.
Both RMI and CORBA make it possible for you to write programs by which a program running on one machine can invoke methods on an object that exists on another machine somewhere on a network. Both approaches make use of concepts known as stubs and skeletons, but at an abstract level.
The purpose of this lesson is to introduce you to stubs, skeletons, and remote objects at a very fundamental level. Once you understand this material, you should have a much better idea of what is going on when you use the RMI and CORBA class libraries to implement distributed object programs.
This is not a polished distributed-object program. Rather, it is a crude implementation of the stub/skeleton concept designed solely to introduce you to that concept.
The program doesn’t know how to terminate the socket connection properly and throws an exception when it terminates. You might want to see if you can figure out how to prevent that. It would also be a good exercise for you to clean this program up and make it more robust.
The program consists of four different classes and one interface that are stored in the following files:
Here are the operating instructions for compiling and executing this program.
The output that appears on the screen where you start RemoteObjClient01 should be:
Desired color is: red
Desired size is: 28
The server will then terminate by throwing an exception. As mentioned above, the program does not know how to shut the server down properly.
The interface file
I often tell my students that if you don’t understand the Java interface, you don’t understand Java. This sample program provides a particularly interesting and important use of the Java interface, so I will begin at that point.
/*File RemoteObj01.java **********************************************************/ public interface RemoteObj01 {
public int getSize() throws Throwable; public String getColor() throws Throwable; } |
As you can see, this is a very simple interface that declares only two methods. The interesting thing about this interface is that two classes in this program implement it, but the two implementations are significantly different.
The remote object class
The objective of this simple program is to make it possible for a method running in an object on one machine to invoke methods on an object that resides on a different machine. (Although the program is written to run in two different processes on the same machine for convenience, it would be very easy to modify it to run on two different machines.)
I will refer to the object whose methods are being invoked as the remote object. In CORBA jargon, this object would be known as a servant object.
The following fragment begins the definition of the class from which the remote object is instantiated. Note that this class implements the interface shown above, which is also implemented by the stub class on the client.
This is a very simple class. When a new object of the class is instantiated, two incoming parameters are used to set the state of the object. Otherwise, there is no code in the object to change its state.
/*File RemoteObjServer01.java **********************************************************/ public class RemoteObjServer01 implements RemoteObj01 { int size; String color;
//Constructor public RemoteObjServer01(String color, int size){ this.size = size; this.color = color; }//end constructor |
Methods of the remote object
The next fragment shows the two methods of the remote object that can be invoked remotely by the client. They are very simple also, simply returning the state of the object that was established when the object was instantiated. I purposely designed this as a simple class to reduce the complexity of the program as much as possible.
public int getSize(){ return size; }//end getSize() //-----------------------------------------------------//
public String getColor(){ return color; }//end getColor() }//end class RemoteObjServer01 |
The skeleton class
The primary purpose of this program is to illustrate the use of stub and skeleton objects to make it possible for a method of an object on one machine to invoke methods of an object on a different machine. The beginning of the skeleton class is shown in the next fragment.
Before getting into the details of the code, it will be useful to provide some information about the overall concept. In effect there are four objects in existence while the program is running. Two of those objects reside on one machine and the other two reside on a different machine. For want of a better terminology, I will refer to the two machines as the client machine and the server machine. An object on the client machine invokes methods of an object that resides on the server machine.
The two objects on the client machine are:
· The client object.
· A stub object.
The two objects on the server machine are:
· A skeleton object.
· The remote object discussed earlier.
The stub object represents the remote object on the client machine. In fact, insofar as the client object knows, the remote object is physically located on the client machine. That is an important aspect of distributed-object systems. The remote objects on which the methods are invoked appear to be on the same machine as the client object that is invoking those methods.
When the client object thinks that it invokes a method on the remote object, it actually invokes a method having the same signature on the stub object. (The class from which the stub object is instantiated must implement the same interface as the class from which the remote object is instantiated.) The stub object then sends a message to the skeleton object on the server asking it to invoke the method having the same signature on the remote object.
The skeleton invokes that method on the remote object and captures the value returned by the method. The skeleton object then sends that value back to the stub, which returns it to the client object. Thus, the cycle is complete. The client object has invoked a method and has received a return value.
The skeleton object
The following fragment shows the beginning of the class from which the skeleton object is instantiated. Note that this class extends Thread.
The constructor for the skeleton object receives a reference to a remote object and saves it in an instance variable named myServer.
/*File RemoteObjSkel01.java **********************************************************/ import java.io.*; import java.net.*;
public class RemoteObjSkel01 extends Thread{ RemoteObjServer01 myServer;
//Constructor public RemoteObjSkel01(RemoteObjServer01 server){ //Save a reference to the remote object this.myServer = server; }//end constructor |
The run method
Since this class extends Thread, it must have a run() method. Recall that the run() method is where the action is for threads.
The following fragment shows the beginning of the run() method.
The first thing that the skeleton object does in the run() method is to instantiate a ServerSocket object and block, waiting for a client to request a connection. (The ServerSocket program presented earlier in this series is much more sophisticated than this program insofar as behaving like a server is concerned.)
public void run(){ try{ ServerSocket serverSocket = new ServerSocket(2000); Socket socket = serverSocket.accept(); |
Connection request received
When a connection request is received, the accept() method returns a Socket object that is connected to the client. The run() method goes into a loop
Open input and output streams
Before the skeleton object can read requests from the client, it must open an input stream on the socket. The next fragment shows the opening of input and output streams capable of reading and writing serialized objects.
while(socket != null){ ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); |
Read and parse incoming request
The next fragment shows the skeleton object first reading, and then parsing the incoming request.
The incoming request is received as a serialized object of the String class. Only two possibilities are allowed:
This is not a robust program and no provision is made to deal with incoming data errors.
Depending on the contents of the incoming string object, the skeleton invokes either the getSize() method or the getColor() method on the remote object. It captures the return value from the method, serializes that value, and sends it back to the client.
String method = (String)inStream.readObject();
if(method.equals("getSize")){ int size = myServer.getSize(); outStream.writeInt(size); outStream.flush();
}else if(method.equals("getColor")){ String color = myServer.getColor(); outStream.writeObject(color); outStream.flush(); }//end else
}//end while loop }catch(Throwable t) {
t.printStackTrace();System.exit(0); }//end run() |
Run the server application
This server runs as an application requiring a main() method. The main() method is very straightforward. First the method instantiates a new remote object initializing it with the color red and size 28.
Then the main() method instantiates a skeleton Thread object, passing a reference to the remote object as a parameter to the constructor.
Then it invokes the start() method on the skeleton object to start the skeleton thread running. At that point, the skeleton object becomes capable of responding to connection requests from a client, and invoking methods on the remote object on behalf of the client.
public static void main(String args [] ){ RemoteObjServer01 remoteObj = new RemoteObjServer01("red", 28); RemoteObjSkel01 skel = new RemoteObjSkel01(remoteObj); skel.start(); }//end main() }//end class RemoteObjSkel01 |
The stub object
The stub object resides on the client and represents the remote object to the client. The class from which the stub object is instantiated and the class from which the remote object is instantiated both implement the same interface. Thus, both the stub object and the remote object implement the same method signatures, but they implement those signatures in significantly different ways.
The class of the remote object implements the methods in a manner that is intended to achieve the desired result. The class of the stub object implements the methods in such a way as to make it possible for the stub to accept method calls from the client and pass those method calls along to the skeleton object, which in turn, passes them to the remote object as shown above.
The next fragment shows the beginning of the class from which the stub object is instantiated, emphasizing the fact that the class implements the RemoteObj01 interface.
The fragment also declares some reference variables that will be used later.
/*File RemoteObjStub01.java **********************************************************/ import java.io.*; import java.net.Socket;
public class RemoteObjStub01 implements RemoteObj01 { Socket socket; ObjectOutputStream outStream; ObjectInputStream inStream; |
Connect to the skeleton server
The next fragment shows the constructor which requests a socket connection to the skeleton server discussed above. As shown here, the request is for a connection to localhost. As you will recall, this nomenclature is used to connect to a server running in a different process on the same machine. If you want to run the server and the client on different machines, replace localhost with the IP address of the machine where the server is running.
public RemoteObjStub01()throws Throwable{//constructor ///Instantiate a network connection to the skeleton. socket = new Socket("localhost",2000); }//end constructor |
Implement getSize() on the stub
The next fragment shows the implementation of the getSize() method on the stub object.
The method begins by getting input and output streams on the socket that is connected to the server.
Then it uses the output stream to send a message to the skeleton server asking the skeleton to invoke the getSize() method on the remote object.
Then it blocks on input while reading the return value from the getSize() method on the remote object that is sent back in serialized form by the skeleton.
When that value is received, it is returned to the client object that invoked the getSize() method on the stub in the first place.
public int getSize( ) throws Throwable{ outStream = new ObjectOutputStream(socket.getOutputStream()); inStream = new ObjectInputStream(socket.getInputStream());
outStream.writeObject("getSize"); outStream.flush(); return inStream.readInt(); }//end getSize() |
Implement getColor() on the stub
The next fragment shows the stub implementation of the getColor() method. This is essentially the same as the getSize() method shown above except that the return type is String instead of int. The fragment is shown here for completeness.
public String getColor()throws Throwable{ outStream = new ObjectOutputStream(socket.getOutputStream()); inStream = new ObjectInputStream(socket.getInputStream());
outStream.writeObject("getColor"); outStream.flush(); return (String)inStream.readObject(); }//end getColor() }//end class RemoteObjStub01 |
The client object
The next fragment shows the class from which the client object is instantiated. The highlighted line of code in the main() method shows the thing that distinguishes the invocation of methods on a remote object from the invocation of methods on a local object.
In the case of the remote object, the main() method is required to instantiate a new object of the stub class that represents the remote object.
If the object on which the methods are to be invoked were a local object, then the main() method would instantiate an object of the class of that object.
In both cases, the reference to the object can be stored in a reference variable of the interface type implemented by both classes.
/*File RemoteObjClient01.java **********************************************************/ public class RemoteObjClient01 { public static void main(String [] args){ try{ //Instantiate the stub that represents the remote // object on the client. RemoteObj01 remoteObj01 = new RemoteObjStub01(); |
Invoke the methods
The next fragment shows the client invoking each of the available methods on the stub object that represents the remote object. The values returned by those method invocations are then displayed.
int size = remoteObj01.getSize(); String color = remoteObj01.getColor();
System.out.println("Desired color is: " + color); System.out.println("Desired size is: " + size); }catch(Throwable t) {t.printStackTrace();} }//end main() }//end class RemoteObjClient01 |
Hopefully, an understanding of this material will help you to better understand RMI and CORBA when you study those lessons.
This section contains listings of all of the programs discussed above.
/*File RemoteObjSkel01.java
This is one of a system of files that illustrates the use of stubs, skeletons, and remote objects in a system of distributed objects.
Operating instructions: 1. Compile all files 2. Start RemoteObjSkel01. The server will start and just sit there waiting for a connection. 3. Start RemoteObjClient01. The output should be:
Desired color is: red Desired size is: 28
The server will then terminate by throwing an exception. The program does not know how to shut the server down properly.
Tested using JDK 1.2.2 under WinNT 4.0 Workstation **********************************************************/ import java.io.*; import java.net.*;
public class RemoteObjSkel01 extends Thread{ RemoteObjServer01 myServer;
//Constructor public RemoteObjSkel01(RemoteObjServer01 server){ //Save a reference to the remote object this.myServer = server; } public void run(){ try{ //Create a server socket and block waiting for a // connection request ServerSocket serverSocket = new ServerSocket(2000); Socket socket = serverSocket.accept(); //If the returned socket is valid, process the // connection. while(socket != null){ //Get input and output streams on the socket ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream());
//Read incoming request. Determine the type of // request and satisfy it. Only two types of // requests are allowed and there is no code to deal // with bad requests. String method = (String)inStream.readObject();
if(method.equals("getSize")){ //Invoke method on remote object int size = myServer.getSize(); //Send results back to stub client outStream.writeInt(size); outStream.flush(); }else if(method.equals("getColor")){ String color = myServer.getColor(); outStream.writeObject(color); outStream.flush(); }//end else }//end while loop }catch(Throwable t) { t.printStackTrace();System.exit(0); } } //=====================================================// public static void main(String args [] ){ //Instantiate the remote object RemoteObjServer01 remoteObj = new RemoteObjServer01("red", 28); //Instantiate the skeleton, linking it to the remote // object and start the skeleton running RemoteObjSkel01 skel = new RemoteObjSkel01(remoteObj); skel.start(); }//end main() }//end class RemoteObjSkel01 |
.
/*File RemoteObjServer01.java
This is one of a system of files that illustrate the use of stubs, skeletons, and remote objects in a system of distributed objects.
See RemoteObjSkel01.java for operating instructions.
Tested using JDK 1.2.2 under WinNT 4.0 Workstation **********************************************************/ //This is the class from which the remote object is // instantiated. Note that it implements the same // interface that is implemented by the stub on the // client. public class RemoteObjServer01 implements RemoteObj01 { int size; String color;
//Constructor public RemoteObjServer01(String color, int size){ this.size = size; this.color = color; }//end constructor //-----------------------------------------------------//
public int getSize(){ return size; }//end getSize() //-----------------------------------------------------//
public String getColor(){ return color; }//end getColor() }//end class RemoteObjServer01 |
.
/*File RemoteObjStub01.java
This is one of a system of files that illustrate the use of stubs, skeletons, and remote objects in a system of distributed objects.
See RemoteObjSkel01.java for operating instructions.
Tested using JDK 1.2.2 under WinNT 4.0 Workstation **********************************************************/ import java.io.*; import java.net.Socket;
//Note that this stub class implements the same interface // that is implemented by the class of the remote object. public class RemoteObjStub01 implements RemoteObj01 { Socket socket; ObjectOutputStream outStream; ObjectInputStream inStream;
public RemoteObjStub01()throws Throwable{//constructor ///Instantiate a network connection to the skeleton. socket = new Socket("localhost",2000); }//end constructor //-----------------------------------------------------//
//A very important concept is illustrated here. The // following two methods are implementations of the // interface that this class implements. However, these // methods serve as local proxies for the actual methods // on the remote object that the client desires to // invoke. public int getSize( ) throws Throwable{ //Get input and output streams on the socket outStream = new ObjectOutputStream(socket.getOutputStream()); inStream = new ObjectInputStream(socket.getInputStream()); // Send a String to the skeleton indicating the method // that is to be invoked on the remote object. Then // get and return the value returned by the skeleton. outStream.writeObject("getSize"); outStream.flush(); return inStream.readInt(); }//end getSize() //-----------------------------------------------------//
public String getColor()throws Throwable{ //Get input and output streams on the socket outStream = new ObjectOutputStream(socket.getOutputStream()); inStream = new ObjectInputStream(socket.getInputStream()); // Send a String to the skeleton indicating the method // that is to be invoked on the remote object. Then // get and return the value returned by the skeleton. outStream.writeObject("getColor"); outStream.flush(); return (String)inStream.readObject(); }//end getColor() }//end class RemoteObjStub01 |
.
/*File RemoteObjClient01.java
This is one of a system of files that illustrate the use of stubs, skeletons, and remote objects in a system of distributed objects.
See RemoteObjSkel01.java for operating instructions.
Tested using JDK 1.2.2 under WinNT 4.0 Workstation **********************************************************/ public class RemoteObjClient01 { public static void main(String [] args){ try{ //Instantiate the stub that represents the remote // object on the client. RemoteObj01 remoteObj01 = new RemoteObjStub01();
//Invoke two methods on the remote object via the // stub and display the returned values. int size = remoteObj01.getSize(); String color = remoteObj01.getColor(); System.out.println("Desired color is: " + color); System.out.println("Desired size is: " + size); }catch(Throwable t) {t.printStackTrace();} }//end main() }//end class RemoteObjClient01 |
.
/*File RemoteObj01.java
This is one of a system of files that illustrates the use of stubs, skeletons, and remote objects in a system of distributed objects.
See RemoteObjSkel01.java for operating instructions.
Tested using JDK 1.2.2 under WinNT 4.0 Workstation **********************************************************/ public interface RemoteObj01 {
public int getSize() throws Throwable; public String getColor() throws Throwable; } |
-end-