Wednesday, May 5, 2010

Tutorial Introduction to Java RMI

 

 

 

The networking we have looked at so far has been based on sockets.  This is the simplest way of doing network programming in Java, but not the cleanest nor the most powerful.  You may also have been disturbed by the fact that the networking primitives did not really seem to be object-oriented.   In later versions of Java, Sun has added a new object-oriented version of networking through a mechanism called remote method invocation (RMI).

In this section you will learn more about the object model by being introduced in a practical way to how the RMI facilities (contained mainly in the java.rmi package) can be used to develop a simple system based on the object model.

Introduction

 

You have already learned about Java's client–server model of distributed programming. In that model, clients are processes (running programs) which reside on one or more host computers, communicating with a server process running a Java program on its host (which usually  is different from the client hosts). The communication between clients and server in this model takes place through ports and sockets, while the messages that make up the communication take the form of streams of data. An application protocol must be established between client and server so that the server knows how to respond to these messages. This means that the messages from the client are not method invocations in the OO sense, but rather a stream of data that must be interpreted by the server before it can invoke methods on the objects it knows about.

In the OO paradigm, a client object sends a message to an object located on a (remote) server host. That is, the client invokes a method on the remote object. In fact, the execution of the method takes place on the remote host where the remote object resides.

The difference between a client–server model as described above and the OO paradigm is more significant than it may appear. It is a non-trivial task to set up communication between clients and a server involving the creation of socket connections and the use of input and output streams. Consider how much easier the Time system you worked with in the Networking tutorial would have been if you could simply have used an object of type Time (even though it was a remote object) and sent a message to it in the form of a method invocation. This is what the RMI mechanism seeks to achieve. You can therefore remain within the object-oriented paradigm rather than sending messages which are only indirectly connected to that paradigm.

Conceptually, what we want to achieve through RMI is represented in Figure 1. This shows a client process made up of many communicating local objects. The local object O3 is shown sending a message to the remote object remO. The three objects named O1, O2 and O3 have been created as part of the client process running on Host A (indicated by the dotted lines that associate the client process with the three objects) whereas remO has been created as part of the server process running on the remote host, Host B (as shown by the dotted line from the supplier process). The arrows between objects indicate the direction in which one or more messages (method calls) are being sent. For example, in Figure 2.1, object O2 is shown as calling a method or methods of object O3. Note that, although the direction of the method call is shown as flowing in one direction (in this case from O2 to O3), the data flow between the objects is likely to be bidirectional, with input data flowing from O2 to O3, and return data resulting from the call flowing from O3 to O2.

Figure1              A client process composed of a set of communicating objects

The conceptual view shown in Figure 1 clearly begs many questions about implementation. For example:

  1. how do the remote objects come into existence?
  1. how do the clients locate remote objects?
  2. how are messages sent from a local object to a remote object?

As you would expect, the answer to these questions involves a number of mechanisms, some of which the developer needs to be aware of and others that go on 'below the surface'. An overall view of these mechanisms is shown in Figure 2, where all the new objects needed for implementing Figure 1 are shown unshaded, and the mechanisms not visible at the application level are shown in the more darkly shaded areas. The virtual communications between components are shown dashed (not to be confused with the dotted lines associating objects with each process); they indicate that, as far as the application is concerned, messages are being sent directly to the objects at the other end of the link. However, the actual implementation of these virtual communication links by means of real communication links will be via layers that are not visible to the application.

Figure 2              Implementation view of the model in Figure 1

Figure 3 is a copy of Figure 2.2 in which the mechanism by which a client can discover where a remote object is located — the RMI registry. The registry is a Java program that keeps a record of where remote objects are kept (it is informed by the server) so that clients can access this information. In general, the registry can be on a third host. In Figure 3, one of the application objects (O1) of client process is shown as sending a message (shown as a virtual method call since it is to a remote machine) to the RMI registry in order to obtain a reference to the remote object rem0.

Figure 3              RMI registry.

The diagram in Figure 3 is best explained by showing how it answers the three questions posed above (the reasons for some of the steps will only emerge when describing the development of the small example application below).

  1. Before a remote object can be created:
  • its operations must be specified in an interface which extends the class Remote from the java.rmi package;
  • the remote object's class, RemObjImpl say, must then be defined by implementing this interface and extending the class UnicastRemoteObject (also in the java.rmi package).

An object of the class RemObjImpl, remO say, can then be declared and created. To make remO available to clients, it must be registered in an RMI registry using a string name as an identifier. This registry can either be located on the same host as the remote object or, as shown in Figure 2.3, on another host.

  1. Clients find out about remote objects by looking in the RMI registry, where each server will have placed information about the remote objects its has created. The RMI registry is itself a remote server-object. Typically, the RMI registry is accessed via the methods of the Naming class. To find a remote object, the developer of a client application must know:
  • the location of the RMI registry;
  • the string name of remO.

In order to obtain a reference to remO, the client application can invoke an operation on the RMI registry using the string name as a key. Provided remO is in the registry under this name, a reference to it will be returned.

  1. In fact, the reference which is returned by the RMI registry is to a stub object, a copy of which must be placed on both the client's machine and the server's machine. On the client host, this stub object acts as a proxy for the remote object, and the messages (method invocations) that the client thinks it is sending to the remote object are in fact being sent to the stub. The stub on the client, in turn, communicates with a similar stub object on the remote machine. It is the stub on the server's machine that then communicates the call to the remote object itself. One of the important tasks of stubs is to marshal and unmarshal the arguments in the remote call and the return value sent back by the remote object. The stubs use Java's client–server mechanism involving sockets and input and output streams but this is invisible to the developers of the client and server processes.

In Figure 2.3, objects called security managers are shown. These will not be discussed in any great detail in this text. Similar to the security systems that control applets, RMI security managers control what remote objects and their clients can do. The developer of RMI applications needs to know about them since both client and server applications must start by creating and installing a security manager:

System.setSecurityManager (new RMISecurityManager ());

 

Without this, RMI will only allow client objects to communicate with 'remote' objects on the same host as the client.

With this background, you should now have an overall view of the RMI model that will enable you to follow the construction of a simple example system.

An example using RMI

The remote object

Before looking in detail at how to use Java's RMI facilities, there are two fundamental ideas about Java objects that it will be useful to review. The first is that, when defining a class, it is often a good idea to do so in two parts: an interface and an implementation. A Java interface contains the signatures of the public methods that can be invoked on an object of the class. The implementation contains the bodies of the public methods together with any fields and private methods that may be required. This separation into interface and implementation has many advantages. In this section the main advantage is that a client (a user of a remote object) needs only to know about the interface of the remote object's class whereas the server (the implementor of the remote object) needs to know about both the interface and the implementation of the class.

The second idea is that an object is created only when a Java program (strictly, a Java thread) is executed. An object exists in main memory until either it is no longer referenced (in which case the garbage collector reclaims the storage it occupied) or the thread that created it ceases execution. Thus, there must be a thread in existence if an object is to exist.

Thus, when there is a remote object, the client must be aware of the object's interface (to ensure that the remote method invocations made by the client can be checked by the compiler). Of course, the implementations of these methods must be known to the server because it is the server host that executes the methods.

This means that we shall develop classes for remote objects in the form of an interface plus an implementation. Both are required by the server (you can't have an implementation without the associated interface) but only a copy of the interface is required by the client.

 

Example

Throughout this section we shall use the simplest BallWorld program, that you first met in Lectures, to illustrate how RMI works. The basic idea will be to create a single Ball object on the remote server and write a client program that displays the BallWorld frame and the ball. Thus, the BallWorld frame will be displayed on the client's screen but all method calls to the Ball object will be sent to the server using remote method invocations.

You may think this is a rather pointless thing to do. However, it mirrors very closely more complex systems in which a large mainframe computer performs the calculations leaving smaller satellite computers to display the results. For example, in weather forecasting, the mathematical calculations are so computationally intensive that only the largest of computers can deal with them in a reasonable time. Using small PCs in weather bureaux across the country to display weather maps using data supplied by the mainframe is a common occurrence.

The first step is to re-implement the BallWorld program given in Budd, Figure 5.2 so that the Ball class is separated into an interface (Figure 4) and an implementation (Figure 5).

The methods for the Ball interface shown here are similar to those contained in an earlier version of Budd. They do not differ substantially from those contained in the current version.

 

 

 

 

 

import java.awt.*;

 

public interface Ball {

                            public void setColor (Color newColor);

              public Color getColor ();

              public void setMotion (double dx, double dy);

              public int radius ();

 

              public void reflectVert ();

              public void reflectHorz ();

              public double changeX ();

              public double changeY ();

              public void moveTo (int x, int y);

              public void move();

}

Figure 4              The Ball interface

 

import java.awt.*;

 

              public class BallImpl implements Ball {

              protected Point loc;

              protected int rad;

              protected double changeInX = 0.0;

              protected double changeInY = 0.0;

              protected Color color = Color.blue;

 

              public BallImpl(Point lc, int r) { loc = lc; rad = r; }

 

              public void setColor(Color newColor) { color = newColor; }

              public Color getColor () {return color; }

              public void setMotion(double dx, double dy) { changeInX = dx; changeInY = dy; }

              public int radius() {return rad; }

              public Point location () { return loc; }

              public void reflectVert () { changeInX = - changeInX; }

              public void reflectHorz () { changeInY = - changeInY }

              public double changeX () { return changeInX; }

              public double changeY () { return changeInY; }

              public void moveTo(int x, int y) { loc.move (x, y); }

              public void move() { loc.translate ((int) changeInX, (int) changeInY); }

}

Figure 5              The Ball implementation

 

 

 

 

Even if we had wanted the server to perform the paint method the Java system would have prevented it because it does not allow Graphics objects to be arguments to remote method calls.

There is one other significant change to the specification of the Ball interface: there is no longer a paint method. The revised application requires the client host to perform the displaying of the BallWorld frame and the Ball, not the remote server.

The application has been changed so that BallWorld performs all the necessary computations for the animation. Figure 2.6 shows the revised application.

import java.awt.*;

import javax.swing.*;

 

public class BallWorld extends JFrame {

 

              public Ball aBall;

 

              public static void main (String args [ ] ) {

                            BallWorld world = new BallWorld (Color.red);

                            world.show ();

                            for (int i = 0; i < 1000; i++)

                                          world.run ();

                            System.exit (0);

              }

 

              public static final int FrameWidth = 600;

              public static final int FrameHeight = 400;

 

              private BallWorld (Color ballColor) {

                            setSize (FrameWidth, FrameHeight);

                            setTitle ("Ball World");

                            aBall = new BallImpl (new Point (FrameWidth/2, FrameHeight/2), 15);

                            aBall.setColor (ballColor);

                            aBall.setMotion (3.0, 6.0);

              }

 

              public void run () {

                            aBall.move ();

                            Point pos = aBall.location ();

                            if ((pos.x < aBall.radius ()) || (pos.x > FrameWidth - aBall.radius ()))

                                          aBall.setMotion (-aBall.changeX (), aBall.changeY ());

                            if ((pos.y < aBall.radius ()) || (pos.y > FrameHeight - aBall.radius ()))

                                          aBall.setMotion (aBall.changeX(), -aBall.changeY ());

                            repaint ();

                            try { Thread.sleep (50); } catch (Exception e) {System.exit (0); }

              }

 

              public void paint (Graphics g) {

                            super.paint (g);

                            g.setColor (aBall.getColor ());

                            g.fillOval (aBall.location ().x - aBall.radius (),

                                                                      aBall.location ().y - aBall.radius (),

                                                                      aBall.radius ()*2, aBall.radius ()*2);

              }

}

The revised BallWorld application


The client side: making remote calls

The application that runs on the client host is a slight variation on the BallWorld application so we have named it BallWorldClient and is shown in Figure 7.

import java.awt.*;

import javax.swing.*;

import java.rmi.*;

 

public class BallWorldClient extends JFrame {

 

              public Ball aBall;

 

              public static void main (String args [ ] ) {

                            BallWorldClient world = new BallWorldClient (Color.red);

                            world.show ();

                            for (int i = 0; i < 1000; i++)

                                          world.run ();

                            System.exit (0);

              }

 

              public static final int FrameWidth = 600;

              public static final int FrameHeight = 400;

 

              private BallWorld (Color ballColor) {

                            setSize (FrameWidth, FrameHeight);

                            setTitle ("Ball World");

                            try {

                                          aBall = (Ball) Naming.lookup ("rmi://localhost/BouncingBall"); // main change

                                          aBall.setColor (ballColor);

                                          aBall.setMotion (3.0, 6.0);

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

              }

 

              public void run () {

                            try {

                                          aBall.move();

                                          Point pos = aBall.location ();

                                          if ((pos.x < aBall.radius ()) || (pos.x > FrameWidth - aBall.radius ()))

                                                        aBall.setMotion (-aBall.changeX (), aBall.changeY ());

                                          if ((pos.y < aBall.radius ()) || (pos.y > FrameHeight - aBall.radius ()))

                                                        aBall.setMotion (aBall.changeX (), -aBall.changeY ());

                                          repaint ();

                                          try { Thread.sleep (50); } catch (Exception e) {System.exit (0); }

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

              }

 

              public void paint (Graphics g) {

                            super.paint (g);

                            try {

                                          g.setColor (aBall.getColor ());

                                          g.fillOval (aBall.location ().x - aBall.radius (),

                                                                                    aBall.location ().y - aBall.radius (),

                                                                                    aBall.radius ()*2, aBall.radius ()*2);

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

              }

}

Figure.7              The client application: BallWorldClient


There are two changes to note in producing BallWorldClient from BallWorld.

  1. Since the Ball object exists on another host and hence must be created on that host, the statement in the original application BallWorld that created the Ball object:

              aBall = new BallImpl (new Point (FrameWidth/2, FrameHeight/2), 15);

The argument to the lookup method is a String containing a URL of the object on the remote host. In the example, the server is on the local machine, hence the use of localhost in the URL.

             

must be changed for a statement that finds the object on the remote server:

              aBall = (Ball) Naming.lookup ("rmi://serverHostIPAddress/BouncingBall");

             

The Naming class is contained in the package java.rmi, and the lookup method provides the client with a reference to the remote Ball object. We shall explain how this happens when we examine the server side. At this point, all you need to know is that an exception could occur when using this method and so it must be contained within a try statement.

  1. If you compare the definitions of the run and paint methods in the BallWorld and BallWorldClient classes, you will see that the calls to the Ball object are identical, just as if the Ball object were on the client's host in both cases. However, since the calls in BallWorldClient are actually remote calls, problems can arise when the associated messages are sent over the network. That is, all remote method calls can throw exceptions. This requires that all remote calls must be placed inside try statements, and any resulting exception dealt with by an appropriate catch block.

You may be wondering how the Java system knows that the calls to Ball within BallWorldClient are remote invocations and not local calls. The answer lies in the Ball interface that needs to be amended as shown in Figure 8.

import java.awt.*;

import java.rmi.*;

 

public interface Ball extends Remote {

              public void setColor (Color newColor) throws RemoteException;

              public Color getColor () throws RemoteException;

              public void setMotion (double dx, double dy) throws RemoteException;

              public int radius () throws RemoteException;

              public Point location ()throws RemoteException;

              public void reflectVert () throws RemoteException;

              public void reflectHorz () throws RemoteException;

              public double changeX () throws RemoteException;

              public double changeY () throws RemoteException;

              public void moveTo (int x, int y) throws RemoteException;

              public void move() throws RemoteException;

}

Figure 8              The amended Ball interface.

This interface extends the java.rmi class Remote and each method throws a RemoteException. This illustrates why all remote calls must be enclosed within a try statement.

The server side: the remote object

On the server side, both the interface and implementation (in their remote forms) of the remote object are required. The remote interface, Ball, has already been exhibited in Figure 8. The remote implementation, BallImpl, is shown in Figure 9.

import java.awt.*;

import java.rmi.*;

import java.rmi.server.*;

 

public class BallImpl extends UnicastRemoteObject implements Ball {

 

              protected Point loc;

              protected int rad;

              protected double changeInX = 0.0;

              protected double changeInY = 0.0;

              protected Color color = Color.blue;

 

              public BallImpl(Point lc, int r) throws RemoteException { loc = lc; rad = r; }

 

              public void setColor (Color newColor) throws RemoteException { color = newColor; }

              public Color getColor () throws RemoteException {return color; }

 

              public void setMotion (double dx, double dy) throws RemoteException {

                            changeInX = dx; changeInY = dy;

              }

 

              public int radius () throws RemoteException {return rad; }

              public Point location () throws RemoteException { return loc; }

 

              public void reflectVert () throws RemoteException {

                            changeInX; = - changeInX;

              }

 

              public void reflectHorz () throws RemoteException {

                            changeInY = - changeInY;

              }

 

              public double changeX () throws RemoteException { return changeInX; }

              public double changeY () throws RemoteException { return changeInY; }

              public void moveTo (int x, int y) throws RemoteException { loc.move (x, y); }

 

              public void move() throws RemoteException {

                            loc.translate ((int) changeInX, (int) changeInY);

              }

}

Figure 9              The implementation of the Ball object: BallImpl

The implementation of the remote object inherits from the rmi class UnicastRemoteObject. This class provides the basic functionality for remote objects. In particular, it exports the object enabling the object to receive remote calls. That is, the remote object can wait for client connections on an anonymous port (a port chosen by the Java system). The result is that the remote object can take part in unicast communications — point-to-point communication between two objects via method calls using standard stream-based socket connections.

The application that creates the Ball object on the server, BallServer, is shown in Figure .

import java.awt.*;

import java.rmi.*;

 

public class BallServer {

 

              public BallServer () {

                            try {

                                          Ball b = new BallImpl (new Point (300,200), 15);

                                          Naming.rebind ("rmi://localhost/BouncingBall", b);  // registers Ball object

                                          System.out.println ("remote ball object registered.");

                            } catch (Exception e) {System.out.println ("Trouble: " + e); }

              }

 

              public static void main (String args [ ] ) {

                            BallServer bs = new BallServer ();

              }

}

Figure 10              The class BallServer

 

The function of BallServer is to create a Ball object and register it in the RMI registry. The latter task is achieved with the statement:

Naming.rebind("rmi://localhost/BouncingBall", b); // registers Ball object in registry

 

The first argument, "rmi://localhost/BouncingBall", specifies where the RMI registry is to be found, in this case on the local host where the BallServer main thread is executed. The Ball object created by BallServer is registered with the name "BouncingBall" and it is by this name that the BallWorldClient identifies the object when it executes the statement

aBall = (Ball) Naming.lookup ("rmi://localhost/BouncingBall");

 

(see the BallWorldClient constructor).

The RMI registry

The RMI registry is a Java program named rmiregistry which which, when it is executing, maintains a list of references to objects that have been registered with it. An object is registered by a server using the rebind method from the class Naming (part of the java.rmi package). The first argument of the rebind method is a String that specifies:

  1. where the registry is located — by quoting the IP address of its host (localhost can be used when the registry is on the same host as the server) and the port on which the registry listens for communications (by default this is 1099, and can be omitted);
  1. a programmer defined name by which clients can gain access to the object.

The second argument is a reference to the object itself. There is an alternative method to rebind known as bind. The difference between them depends upon whether or not an object with the same string name already exists within the registry when these methods are invoked. If an object already exists, rebind will replace its reference with a reference to the new object whereas bind will throw an exception.

A client gains access to a remote object by executing the method lookup, also from the Naming class. This method has a single String argument as in the rebind method that specifies where the registry is located (the registry host's IP address and port number) and the String name by which the registry knows the object.

The registry program rmiregistry is part of the Java system.

There is another useful method in the Naming class, namely list. This method returns a list of all the names of objects currently supported by the registry.

It is important to recognise that the RMI registry is a non-persistent scheme. The registry forgets all its contents when the rmiregistry program stops.

The stub object

An important point to note from Figures 2 and 3 is that the client process does not communicate directly with the remote object. Instead, there is a stub object on the client's host that acts as a proxy for the remote object. Therefore, whenever the client invokes a method on the remote object, the message is sent to the stub, which is responsible for marshalling the arguments and converting the message into a stream object capable of being transmitted to the remote host using Java's socket and stream client–server mechanism. Once the message reaches the remote host, it is transformed into a normal method invocation applied to the remote object.

Any response (return value) from the remote object follows the reverse procedure. The stub on the client converts the return message into a value that is returned to the object that made the original remote call.

The stub object is created from the class that implements the remote object, BallImpl, using a special program named rmic as we shall illustrate in the next subsection.

How to get it to work

Read the document that is linked as RMI Practical from the CIS332 Website. It contains information about paths and classpaths that you may find helpful, and lists the steps you need to carry out in order to get RMI to work on your machine. It also contains some trouble-shooting advice.

The following checklist shows how to run a client program (BallWorldClient) that uses RMI to access a remote object (a Ball) created by a server program (BallServer). In this example, the client, server and registry will all be located on your computer (localhost).

  1. To avoid problems later as the result of having to type long lines of commands, set your operating system's PATH and CLASSPATH environment variables as follows (the method depends upon which operating system you are using).
  • For Windows NT: click on the Start menu, select Settings then Control Panel. Double click on System and select the Environment tab.

If there is a CLASSPATH variable already defined in the User Variables area, click on it, otherwise click in the User Variables area (i.e. the lower pane), and in the Variable edit box, type CLASSPATH. In the Value box, type the locations of the files where your client and server classes will be stored (adding them to any values already set), for example:

              C:\JavaProjects\rmi\BallWorldClient\classes;C:\JavaProjects\rmi\
BallServer\classes

 

Then press SET.

These paths should be separated from any existing paths by a semi-colon (;). Do not, under any circumstances, introduce spaces, not even between paths.

If there is a PATH variable already defined, click on it, otherwise, in the Variable edit box, type PATH. In the Value box, type the location of the jdk that came with JBuilder35 (adding it to any values already set), for example:

                                          C:\JBuilder35\jdk1.2.2\bin

 

Press SET, followed by OK.

  • For Windows 95 and Windows 98: Add the following paths to the
    SET CLASSPATH and PATH statements in the AUTOEXEC.BAT file as follows:

                                          SET CLASSPATH=c:\javaprojects\rmi\ballworldclient\classes;c:\javaprojects
                                          \rmi\ballserver\classes

                                          PATH=c:\jbuilder35\jdk1.2.2\bin

 

  1. Make sure you do not introduce any spaces, not even between the two classpaths which should be separated by a semi-colon (;) only. Restart your machine for these changes to take effect.
  1. Create a new project in JBuilder35 for the BallServer. Name the project

              C:\JavaProjects\rmi\BallServer\BallServer

 

  1. Add the files Ball.java, BallImpl.java, and BallServer.java to this project from the folder C:\JavaProjects\rmi\BallServer\src.
  2. Build the project (right click on BallServer.jpr and choose Make) to produce class files (which will appear in C:\JavaProjects\rmi\BallServer\classes).
  1. Create a stub as follows.
  • Open a new MSDOS window.
  • Create the stub file using the rmic program by typing the following command at the prompt (this assumes that you have set the CLASSPATH and PATH variables as indicated in step 1). Ensure that you type the filename BallImpl, with upper-case letters as shown.

                                          rmic -v1.2 BallImpl

 

  • This should create a new class file named BallImpl_Stub.class in the same directory as the other class files created in step 2. This may take your computer a few seconds to complete. You will know when the operation is complete by the appearance of a new prompt.
  1. Create a new project in JBuilder35 for the BallWorldClient. Name the project

                            C:\JavaProjects\rmi\BallWorldClient\BallWorldClient

  1. Add the files Ball.java, and BallWorldClient.java to this project from the folder C:\JavaProjects\rmi\BallWorldClient\src.
  2. Build the project (right-click on BallWorldClient.jpr and choose Make) to produce class files.
  1. Make a copy of the stub file, BallImpl_Stub.class and add it to the file where the BallWorldClient.class file is stored (this should be

              C:\JavaProjects\rmi\BallWorldClient\classes).

 

  1. In an MSDOS window start the RMI registry running by typing

              rmiregistry

 

  1. You must leave this program running. Do not type anything else in this DOS window. Do not close this window down. Move directly on to the next step.
  1. In a new MSDOS window start the BallServer running by typing

              java BallServer

 

  1. You should see the message remote ball object registered. You must leave this program running. Do not type anything else in this MSDOS window. Do not close this window down. Move directly on to the next step.
  1. In a new MSDOS window start the BallWorldClient running by typing

              java BallWorldClient

 

  1. After a few seconds you should see the BallWorld animation running in its own window.
  1. You can halt both the BallServer and the rmiregistry by typing ctrl C in their respective MSDOS windows. The BallWorldClient will stop of its own accord after a few seconds.

Summary of the steps

Client side

Server side

Write application that uses the remote object

Build an object

Connect to remote host

Register the object

Classes required

 

Client application (BallWorldClient)

Server application (BallServer)

Interface of object (Ball)

Interface of object (Ball)

Stub (BallImpl_Stub)

Implementation of object (BallImpl)

RMI packages required

 

java.rmi.*

java.rmi.*

java.rmi.registry

java.rmi.registry

 

java.rmi.server

RMI classes used

 

Naming (in BallWorldClient)

Naming (in BallServer)

Remote (in Ball)

Remote (in Ball)

 

UnicastRemoteObject (in BallImpl)

To compile and run

 

Compile BallWorldClient.java, and Ball.java

Compile BallServer.java, Ball.java, and BallImpl.java

 

Create BallImpl_Stub.class with rmic

Copy BalllImpl_Stub.class from server

 

 

Start rmiregistry running

 

Start BallServer (to create and register object)

Start BallWorldClient

 

Summary of the steps (continued)

Client side

Server side

Programming actions

 

Communicating over a network: the Security Manager

So far in this discussion, the client, server and RMI registry have been running on the same host. As soon as you try to execute the same programs over a network you run into the Java security measures. As with applets, Java has a security mechanism for RMI that prevents remote method invocations unless additional actions have been taken.

The changes to the code are simple and apply to both the server and the client: they each have to have a security manager installed. That is, a statement of the following form must be added to BallWorldClient and BallServer in our example.

System.setSecurityManager ( new RMISecurityManager ());

 

Figure 11 shows the revised server (we have renamed it SecureBallServer).

import java.rmi.*;

 

public class SecureBallServer {

 

              public SecureBallServer () {

                                          // Create and install a security manager

                            if (System.getSecurityManager () == null) {

                                          System.setSecurityManager ( new RMISecurityManager ());

                            }

                            try {

                                          Ball b = new BallImpl ( new Point (300, 200), 15);

                                          Naming.rebind ("rmi://remotehost/SecureBouncingBall", b);

                                          System.out.println ("remote ball object registered.");

                            } catch (Exception e) { System.out.println ("Trouble: " + e); }

              }

 

              public static void main (String args [ ]) {

                            BallServer bs = new SecureBallServer();

              }

}

Figure 11              The BallServer with security manager

 

The installed security manager requires additional information about the level of security to be applied to the server. Essentially this consists of granting permission to specified clients to access the server. This is achieved by the following three steps.

  1. Create a text file containing the desired permissions. For example, the following allows unconditional access to all clients.

              grant {

                            permission java.security.AllPermission;

              }

 

  1. Issue the following command at an MSDOS prompt.

              java -Djava.security.policy=policy.txt SecureBallServer

 

Here, policy.txt is the name of a text file containing the permissions in step 1 and must be stored in the same directory as the SecureBallServer.class file. This step must be executed before the stub class is created.

  1. When copying the BallImpl_Stub.class file to the directory in which the client class files are stored, also copy the policy.txt file to the same directory.

 

Comparison of RMI with the client–server socket mechanism

RMI is a higher level of abstraction than socket-level programming. It enables the details of socket servers, sockets and data streams to be hidden. Although we have not explicitly mentioned it, RMI uses a hidden multithreading system that would otherwise have to be implemented in a socket-layer.

RMI clients can invoke a server method directly but socket-level programming allows only values to be passed that must then be decoded and turned into a method call by the server. This decoding is performed automatically by RMI stubs (marshalling).

RMI programs are much easier to maintain than socket-level programs. An RMI server can be modified or moved to another host without the need to change the client application (apart from resetting the URL for locating the server).

RMI is implemented using socket-level programming. Socket-level programming is viewed as a primitive mechanism that is prone to error and you should use RMI in preference. This is similar to the relationship between semaphores and higher level constructs such as monitors.

In the conventional client–server mechanism, a client sends a message to the server that replies with a result. The reverse is not possible: a server cannot invoke the methods on a client. However, the RMI mechanism supports the idea of callbacks in which the server invokes methods on the client. This facility enables interactive distributed applications to be developed.

While the details of this mechanism are outside the scope of this unit, the following example illustrates what has to happen. The Internet is becoming popular for game playing among groups of people. Each player in a group runs a client program that sends remote method invocations to a central client. The central client's purpose is to coordinate the players' activities by maintaining the state of the game and communicating each player's moves to the other players. As part of this process, the server invokes client methods. However, a server cannot invoke a client's methods directly because the client is not a remote object. However, through Java's callback mechanism, each client passes a stub (of an implementation of a special CallBack interface) to the server. The stub contains an instance of the client so that the server can invoke the client's methods.

Self-Assessment Questions and solutions

Question 1

What is the essential difference between the client–server model and an object model like RMI?

Solution

The object model works within the OO paradigm, in which services are obtained by sending messages (method invocations) to objects.

The client–server model also works on the basis of a client sending messages to a server. However, in this case the messages are not in the form of method invocations, but conform to another protocol agreed between client and server, such that the server can invoke methods on its encapsulated objects on behalf of the client. This does mean, however, that the client and the server have to include code to transform method invocations into data streams and vice versa in order to communicate.

 

Question 2

What are the essential steps in creating an RMI system?

Solution

The essential steps in creating an RMI system are as follows.

  • Define an interface for the remote object (it must inherit from Remote).
  • Write an implementation for the remote object (the class must inherit from UnicastRemoteObject).
  • Use the rmic program to create a stub (for use by the server and the client).
  • Write a server program to create an instance of the registered remote object and register it with the RMI registry.
  • Write a client to use this remote object: first by obtaining a reference to the remote object from the RMI registry; and second by invoking the remote object's methods by sending OO-style messages to it.

Question 3

When are stub objects created and what is their function?

Solution

A stub object is created by the rmic program from the implementation of the remote object. The reference to the remote object that the client receives from RMI registry is actually a reference to the stub, because it is the stub's role to deal with the messages (by marshalling arguments, and so on.) that the client wishes to send to the remote object. The stub does this by sending messages to the stub on the remote object's host, which in turn communicates them to the remote object. One of the tasks of the stub objects is to marshal and unmarshal arguments and return values.

 

Question 4

In what way does the RMI model not conform to the object model?

Solution

The client cannot create objects of the remote object's class at will. It can only use remote objects that have already been created and registered. For example, given the class Ball, a client class can create as many local objects of class Ball as it requires. In contrast, with remote objects, the client is confined to using only those objects that are registered in the RMI registry.

 

.


1 comment: