Section 5.8. Introducing RMI


5.8. Introducing RMI

Remote Method Invocation (RMI) is a system for distributing application code over multiple hosts. It is a small part of multitier computing. Much of this book will be devoted to the how's and why's of multitier client/server computing. Here we are concerned only with the SDK tool rmic, the RMI compiler.

5.8.1. A Brief Introduction to RMI

Remote Method Invocation is a basic client-server model for invoking Java methods over a network.

5.8.1.1 History and Background

One of the most common problems in computing is how best to make an application available to the largest number of users at the least cost. To this end we have seen the development of "timeshare" systems with dumb terminals all over the place. We have seen the evolution of distributed GUI systems with X Windows and its network display system, and with tools like VNC (Virtual Network Console).[16] We have seen the emergence of the PC, providing autonomous computing at each worker's desktop. And finally we have seen the desktop turning slowly back into a terminal (albeit a prettier one) with the emergence of client-server computing.

[16] http://www.realvnc.com/

What seems to have emerged from this progression is two major kinds of software systems. One is the PC and associated hardware and software. Developments here have dramatically increased the power and productivity of individual work. The PC revolution was indeed a real change throughout the world of business and technology. But even with this, there are a host of applications and business functions that require a collection of data and resources to be available to multiple workers at the same time. This is the second major kind of software systems. This second kind used to be the only kind, but now it may, in a sense, be the minority of applications, but the most critical to an operation. This second class of system has come to be called enterprise systems.

In enterprise computing, we have the same problem we opened with: How do you make the information and resources available to everyone who needs them at the lowest cost? And the answer is (as it always is) "that depends."

These days, one of the most common solutions is to use a Web server to publish an application. This works well for a great many applications and it is much easier to do than many other methods. That explains its popularity. The Web interface is quite limited, however, so for more user interface intensive applications, client-server computing evolved. To us techies, all of this stuff is client-server. In this context however, client-server refers to a 2-tier system where the UI and logic exist in a GUI application on a PC and common resources are in an SQL database all the clients share.

This is also commonly used, but becomes expensive in a couple of cases. The first is when the database itself becomes a bottleneck because the number of users grows and grows but only one database can exist. The second is simply the cost of maintenance. Since the logic exists in the client, any change to the logic requires updating the software on all clients. Even when a scheme for automating this exists, it is still time-consuming and costly to get all the changes out to all users simultaneously. There are workarounds for both of these issues, but here we are concerned with a different solution altogether.

So, how can we have a UI richer than with a Web application but avoid the pitfalls of the traditional 2-tier client-server computing? The answer is to separate the UI from the business logic and the business logic from the underlying data store. This results in 3 tierspresentation, business logic, and data.

Much of this book will concern itself with 3-tier computing solutions. Java has four major architectures for building 3-tier solutions. One of them is RMI.[17]

[17] The others are Enterprise JavaBeans, servlets, and JavaServer Pages. The latter two are Web-based, and therefore suffer from the UI deficiencies of Web forms, but Sun calls them part of Enterprise Java, so we will too.

5.8.1.2 RMI Basics

RMI works by sharing an interface between the client and the server. The interface groups together the methods that a client may call on a server. A class is written on the server side that implements the interface, and a special compiler is used to generate stubs for the server side and the client side. On the client side, a call to an RMI method looks like any other method call, but it is sent across the network to the server, where the actual instructions are carried out. Any return value is then passed back over the network to the client.

We will walk you through a very simple (and very pointless) example just to show you the tools.

5.8.1.3 Writing the Interface

Our interface is pathetically simple. It is a class that sums two integer arguments and returns an integer result. Example 5.10 shows the interface file.

Note

The names of the classes in the following examples may seem a bit strange, and they are. It is because we aim to build on this example later.


Example 5.10. The Session interface
 package net.multitool.RMIDemo; import java.rmi.*; public interface Session extends Remote {   public int add(int x, int y) throws RemoteException; } 

The two important things to note here are that the interface must extend java.rmi.Remote and that any remote method must be defined as throwing java.rmi.RemoteException. If anything goes wrong during an RMI call, like someone tripping over a network cable, the call will not complete successfully and an exception of that type will be thrown. It is not possible to have a RMI method that cannot throw this exception.

Beyond those features, you can see that defining remote methods is quite familiar and easy.

5.8.1.4 Writing the Server Class

An interface is an "empty vessel." Before any interface can be used, you must have an actual class that implements the interface. In an RMI application, the implementing class is the server (Example 5.11).

The class is named SessionImpl to emphasize its relationship with the Session interface. There is no requirement to match up such names. Likewise, the RMI name given, //penfold/Session, uses the interface name, but it could use any name. It is a good idea to develop a naming convention for RMI interfaces and their implementations. It is critical to develop naming conventions for RMI registry names, particularly in production environments. Without a naming convention, it is difficult to avoid confusion and even chaos. What happens when multiple business units develop RMI code destined for a single production server, and they have all made an RMI interface named Session, or Payment?[18] Bad things happen.

[18] One solution is to use a more advanced naming system, such as LDAP. See Section 21.3.2.3.

There is no "one size fits all" naming convention that we can offer. Possibilities include using package names in RMI registry names, using some element of the business area as a component of the name (such as AccountingSession, ShippingSession, ExecutiveSession). All that matters is that an unambiguous standard be created and followed.

Let's spend some time talking about what this code does.

First, notice that the class extends UnicastRemoteObject. This is not necessary, but using that as a base class saves a lot of server setup. There are times when you would want to do such setup manually, but for our purpose here it saves a lot of effort. The class also implements our remote interface.

The first method is a constructor that calls the superclass constructor. At first glance, this is pointless. Any Java class gets a default constructor that just calls the superclass constructor, so why is this here? It is here because the superclass constructor throws RemoteException. If we didn't define a constructor like the one here specifying that it throws RemoteException, the compiler would complain that there is an unhandled exception. So we define a constructor identical to a default constructor except that it specifies that it can throw the exception.

Example 5.11. The Session server implementation
 package net.multitool.RMIDemo; import net.multitool.RMIDemo.*; import java.rmi.*; import java.rmi.server.*; /** SessionImpl is the server class for the Session RMI interface.  */ public class SessionImpl   extends UnicastRemoteObject   implements Session {   /** Constructor needed to ensure call to UnicastRemoteObject    * constructor and to thus propagate the possible exception.    */  public SessionImpl() throws RemoteException {    super();  }  /** A static main() for the server. */  public static void main(String[] arglist)  {    if (System.getSecurityManager() == null) {      System.setSecurityManager(new RMISecurityManager());    }    String rmiName = "//penfold/Session";    try {      Session adder = new SessionImpl();      Naming.rebind(rmiName, adder);    } catch (Exception e) {      e.printStackTrace();    }  }  /** Implementation of the RMI method, add. */  public int add(int x, int y) throws java.rmi.RemoteException  {    return x+y;  } } 

Next, we have the server main() method. It first sets a security manager. The security manager controls what the VM is allowed to do. A number of default security managers are provided, and here we use one that is designed specifically to give safe and reasonable defaults for RMI applications. You can, of course, write your own security manager. Security managers use "policy specifications" to alter their capabilities. For now, we will explain enough to run a simple example. See Section 5.8.4.2 for more information on policies for our example.

Remember that main() is static, so there is no instance of SessionImpl yet, and thus also no instance of Session. We declare a variable of type Session, and set it to a new instance of SessionImpl. (There is no need to typecast here because SessionImpl implements Session, therefore SessionImpl is, among other things, a Session.) We now have an instance of the server class.

Next, the server must make itself available to the world. It does this by registering itself with the RMI registry (see Section 5.8.3). This is done through a static method of the java.rmi.Naming class, rebind(). Put simply, this maps a remote object to a string name in the registry. When clients contact the registry looking for a name then, if a remote object is mapped to that name, the communication can take place (yes, we are simplifying at the moment). The call to rebind() does not return. The server is up and running.

Finally, we have the implementation of our remote method, add().

This looks like a lot of hassle to go through, and it is, but consider writing an interface that offers, for example, methods like getdirContents(), chDir(), downloadFile(), uploadFile(). You've just written something like an FTP server. No matter how many methods you add to your interface, the complexity of the setup code does not increase. Maybe now it looks a little more useful?

5.8.1.5 Writing the Client Class

At this point, Example 5.12 should be fairly obvious. Our class has just a single static method, main(). It, like our server side main(), sets up a security manager. It then contacts a registry on the machine named penfold looking for an instance of a remote interface named Session (again, lookup() is a static method of the java.rmi.Naming class). We store that reference in a variable of type Session called sess. We can then call the add() on sess. We'll show the server and client running shortly.

Example 5.12. The RMI client program
 package net.multitool.RMIDemo; import java.rmi.*; public class Client {   public static void main(String[] arglist) {     if (System.getSecurityManager() == null) {       System.setSecurityManager(new RMISecurityManager());     }     try {       String name = "//penfold/Session";       // Obtain reference to the remote object       Session sess = (Session) Naming.lookup(name);       System.out.println("Pointless RMI Client. 47 + 13 = " +                             sess.add(47,13) + ", right?");     } catch (Exception e) {       e.printStackTrace();     }   } } 

5.8.2. The rmic Tool

In order for a remote object to make itself available and in order for a client to be able to call such an object, each method needs a client and server-side stub to proxy the method call. Arguments to the method call are converted to streamable data (this process is called marshaling) by the client stub, and that data is sent over the network to the server stub, which must convert that stream into object instances on the server side (this is called unmarshaling). The server-side stub then calls the actual method implementation. When the method returns, any return values and changes to the state of the arguments must be marshaled by the server stub and sent back to the client stub where they are unmarshaled and stored in the correct locations on the client.

This was the traditionally painful part of writing multitier clients. What rmic does is automate the generation of these stubs, so writing a remote method is only slightly more difficult than writing any other method.

To generate RMI stubs for our application, run rmic[19] against the class that implements the remote interface:

[19] Be careful! If you have one or more Java SDKs installed and you have the GNU Compiler for Java installed, watch out for your PATH. The Java compiler and the Java runtime from the JDK don't collide with gcj because the compiler has a different name and gcj compiles to native binaries. But gcj does have an rmic compiler, and it is usually in /usr/bin, which is usually ahead of your JDK in the executable path. If you run rmic and it explodes with errors, make sure you aren't running the rmic from gcj against .class files from a JDK. (And, yes, this bit me and had me confused for a while!)

 penfold$ rmic net.multitool.RMIDemo.SessionImpl 

When you are writing "traditional" Java RMI, that is just about all you need to know about rmic. The program actually has a large number of options and switches, but most of these are to support alternate protocols and systems, such as CORBA IDL and IIOP. If you know what these are, and make use of these, you will find details on these options in Sun's rmic tool documentation.[20]

[20] http://java.sun.com/j2se/1.4.2/docs/tooldocs/solaris/rmic.html

5.8.3. The rmiregistry Tool

The rmiregistry is a naming service that binds RMI server stubs to simple names. Invoking it is incredibly simple. Just type rmiregistry. You may want to run it on other than the default port (1099). For that, just specify the port number on the command line:

 $ rmiregistry 21099 & 

That example shows us running a registry on port 21099 and running it in the background. You might want to use a nonstandard port in order to run a test version of the service while the production version remains available on the standard port.

That is just about all there is to rmiregistry. You can find details in the Sun Java SDK documentation.

5.8.4. Setting Up Servers and Clients

So far, we have written an RMI interface, a server implementation, and a client implementation. We have generated RMI stubs for our RMI object. We are almost ready to fire our system up and give it a try. But first, we'll give you some information about our sample environment and talk very briefly about security.[21]

[21] We're going to gloss over this subject for now.

5.8.4.1 What RMI Servers and Clients Need to Be Able to Do

RMI servers and clients need to be able to listen for connections on network ports, and they need to be able to initiate connections on network ports. Back in the Java 1.1 days, there were no limits on what RMI methods could do. The CLASSPATH was assumed to be trusted. With the RMI 1.2 protocol specification, the ability to actually pass bytecodes between VMs over RMI was added. That means that it is possible for clients to pass code to servers. Obviously, this opens a lot of possible security risks. For this reason, RMI got a security management layer. It is the same security manager as the one that applets use. It also provides a default security manager class that has virtually all such capabilities safely turned off. We need to turn on some of these capabilities in order to make our sample work.

The RMI system expects Java classes to be made available through one of two paths.

  1. The CLASSPATH, either the environment variable or on the command line.

  2. Through a property that points at URL. This URL may be a file: URL, or an http: URL.

We are going to do the simplest case for now. We will have our compiled code installed on both our server system and our client system. The classes will all be referenced relative to the default classpath (in other words, relative to ".", the current directory).

This is not the typical case. The most common case will be for the classes to be available in a JAR file via a Web server, and for the java.rmi.server.codebase property to be set to point to the JAR file via an http: URL.

Example 5.13. A Java security policy file suitable for the RMI example
 grant {   permission java.net.SocketPermission "*:1024-65535", "connect,accept";   permission java.net.SocketPermission "*:80", "connect,accept"; }; 

5.8.4.2 Our Environment

We have two machines. One, penfold, is our server machine. The other, grovel, is our client machine. To keep things straight in our samples, the shell prompts will have the host names in them.

If you are using a JDK that supports the 1.2 RMI specification (and we hope you areit's in all current JDKs), you have to give your server and your client permission to access the network ports needed to run. By default, the Java runtime will look for a security policy file in the home directory of the user running the VM. The default name of the file is .java.policy. Example 5.13 shows what we suggest you put in this file, at least to run this example.

Note

You will have to put this in your home directory both on the server and on all client machines.


5.8.4.3 Compiling and Running the Server

Our packages here follow Sun's suggested naming convention of your domain name, reversed, followed by your package names. It so happens that Mr. Schwarz's domain is called multitool.net (named after his first book, Multitool Linux), so we put all of these classes in a package called net.multitool.RMIDemo.

For all of the examples in this section, as well as the following section on building and running the client, assume that our current working directory is the directory that contains the net directory of our source code.

The output you see in Example 5.14 includes the result of running our client once. Note that the SessionImpl class doesn't terminate. It keeps running to service clients indefinitely.

Example 5.14. Compiling and running our server on penfold.
 penfold$ javac net/multitool/RMIDemo/SessionImpl.java penfold$ rmic net.multitool.RMIDemo.SessionImpl penfold$ rmiregistry &  17286 penfold$ java net.multitool.RMIDemo.SessionImpl Asked to add 47 and 13 

5.8.4.4 Compiling and Running the Client

Example 5.15 shows the actual steps we ran to build and run the client.

Example 5.15. Compiling and running our client on grovel
 grovel$ javac net/multitool/RMIDemo/Client.java grovel$ javac net/multitool/RMIDemo/SessionImpl.java grovel$ /usr/java/jdk/bin/rmic net.multitool.RMIDemo.SessionImpl grovel$ java net.multitool.RMIDemo.Client Pointless RMI Client.  47+13=60, right? grovel$ 

Note

We compile the server class, SessionImpl, on the client side and run rmic against it just to produce the stubs the client requires. You could copy the stub classes from the server machine, or you could put them in a JAR file, put that file on a Web server, and have the java.rmi.server.codebase property point to that JAR file. We're taking the simple way here, but in a real implementation, you would not do it this way. We'll cover more realistic cases later.


5.8.5. RMI Summary

RMI greatly simplifies the business of writing multitier client-server applications. It is suitable for many classes of distributed computing problems, but it does lack several features that required in large, mission-critical applications. For one thing, it lacks any sort of transaction support. If a method invocation fails, the client may not know for certain whether the server finished some work, like writing to a database, before the failure. Also, the rmiregistry program is a very simplistic naming/lookup system. Clients must know where to find the registry with the resources they need.

RMI is very useful for problems of a certain scale, but it is not, in and of itself, sufficient for high-volume, highly available, mission-critical enterprise systems.[22] But that is what J2EE and EJB are for. We'll deal with those in Part V later in the book.

[22] If that sentence did not cause you to get "buzzword bingo," then you aren't trying.



    Java Application Development with Linux
    Java Application Development on Linux
    ISBN: 013143697X
    EAN: 2147483647
    Year: 2004
    Pages: 292

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net