Java Remote Method Invocation (RMI)

Tutorial

Implementing a Remote Command Server in RMI

Version 1.0

 

Author: Brad Gibson


Overview

This document will describe how to implement a Java RMI solution.  This document is not intended to explain all the behind the scenes details of RMI.  It is meant to show an experienced developer how to quickly implement a RMI solution or as quick refresher for experienced RMI Java developers.

 

 

 

History

This tutorial was created Brad Gibson and is based on his implementation of the Remote Command Server created for the recent project.  If you have any questions or comments about this tutorial please feel free to send the author an email.

 

This system was designed to run remote commands on a Red Hat 7.1 Linux server from a Windows 2000 server running Cold Fusion 4.5.  The actual implementation used custom Cold Fusion tags to send the remote commands.  This tutorial is only based on the RMI implementation and does not go into details about the Cold Fusion configuration and installation.  The client side will be implemented in a command line application that can be run from a command window or shell.

 

 

 

Requirements

User

This document expects that the user be fairly experienced Java developer and understand object oriented programming.  It also expects that the user know how to use both Linux and Windows.  The user must have root or administrator rights on both the client and server systems for this example to work as is

 

Hardware and Software

The RemoteCmdServer example is designed to be implemented on two different servers.  The example implements the example using on Windows 2000 system (Client) and on Linux system (Server).  The following hardware requirements are appoximate.

 

Server Machine

Hardware

*   Pentium Class Machine

*   64mb RAM

Software

*   Red Hat Linux 6.1 (or your favorite flavor)

*   JDK 1.2.2 Standard Edition

*   com.gibsonsystems.rmi.RemoteCmdServer

 

Client Machine

          Hardware

*   Pentium Class Machine

*   64mb RAM

Software

*   Windows 95, 98, 2000 Operating System

*   JDK 1.2.2 Standard Edition

*   com.gibsonsystems.rmi.RemoteCmdInterface

 

 

Installing the RemoteCmdServer Example

There are to installations that must be performed, the client side installation and the server side installation.  The server side will be installed on the Linux server that herein will be referred to as a machine with the name of hslinux.

 

The Linux server should be already installed with the operating system and configured to access the local network.  This example uses a 192.168.0 IP address scheme with a subnet mask of 255.255.255.0.  This may or may not conflict with the IP constraints you must work with.  Please contact your network administrator to be sure.

 

Server Side Installation

The server portion of the example is packaged up into a tar file: remotesvr.tar.  This is a Unix archive file. All of the files you need to implement both the server and client portion of this tutorial are contained in this archive file. Transfer this file to the unix server and place in a temporary directory. To extract the file run the following commands from the directory you placed the file.

 

*   mkdir /usr/local/remotesvr

*   tar –xvf  remotesvr.tar /usr/local/remotesvr

 

This will extract the files you need into the home directory of the remote command server, /usr/local/remotesvr. 

 

You will now need to set the CLASSPATH environment variable.  To add the to your classpath run the following command from the command shell.

 

*   set CLASSPATH=$CLASSPATH:/usr/local/remotesvr; export CLASSPATH

 

This sets you classpath for the current shell.  If you wish to add it to all shell by default you must add the above command to you .profile file in your home directory.  Please contact you system administrator for instructions on how to do this.

 

Client Side Installation

The client portion of the example is packaged in the zip file:  remotesvrclient.zip.  All of the files to implement both the server and client portion are contained in this archive file.  Copy the file to your client machine and extract it to c:\.  This will extract the files to c:\apps\remotesvr.

 

You will now need to set the CLASSPATH environment variable.  Add the c:\apps\remotesvr directory to your classpath.  See your windows documentation on how to do this.

 

Running the Remote Command Server

 

To start the remote command server start a shell and run the following command:

*   rmiregistry

 

This shell is now acting as the RMI registry, which runs by default on port 1099.  Now you can start the RemoteCmdServer.  To start the server open a new shell and change to the /usr/local/remotesvr directory.  You can start the server by running the following command:

*   ./run

 

Detailed Code Review

 

This section tutorial will now take you through creating each of the classes that were used to implement this RMI solution.  The Remote Command ‘application’ consists of 2 classes and one interface.  There are also to additional classes created by the rmic (RMI compiler).  The package name is com.gibsonsystems.tutorials.rmi.

 

*   RemoteCmd (Server side implementation)

*   RemoteCmdTestClient (Client side implementation)

*   RemoteCmdInterface (Interface that RemoteCmd implements)

*   RemoteCmd_Stub – (Stub class created by rmic)

*   RemoteCmd_Skel – (Skel class created by rmic)

 

 

RemoteCmdInterface

We will start by creating the interface, RemoteCmdInterface.  This class will define what methods are implemented in the remote command server.  The code for this class (see figure 1) is very simple; all that is needed is to define the return type, method name, and the parameters that will be used.

 

 

package com.gibsonsystems.tutorials.rmi;

 

import java.rmi.Remote;

import java.rmi.RemoteException;

 

public interface RemoteCmdInterface extends Remote {

  String runRemoteCommand(String remoteCommand) throws RemoteException;

}

 

Figure 1

 

 

The package is set to com.hedstrong.tutorials.rmi and the required classes are imported.  Since this interface extends Remote and throws a RemoteException these are both imported.  These two classes will always be imported when creating an RMI interface class.  The class is defined as public and of the type interface.

 

This class has one line that defines the only method that any class implementing this interface must implement, runRemoteCommand(String remoteCommnd).  The remoteCommand string is the command that the calling program wishes to run. This method will return a String type, which is the output of the command.  This string contains both standard out and standard error from the remote machine.

 

RemoteCmd

This class is where the actual actions are taken to run the command and return it’s output.

 

This class implements the RemoteCmdInterface and therefore must have all methods required by the RemoteCmdInterface.  The class also contains a main method that is used to register the class with the RMI registry and the security manager.

 

The class is made up of a constructor method, a main method, and the runRemoteCommand method.  We will start by describing the main method as it is where the RMI magic happens. 

 

The main method performs two tasks, it registers the class with the SecurityManager and binds the methods to the RMI registry.  The code snippet in figure 2 is the main method from the RemoteCmd class. For a full listing of the RemoteCmd class see Appendix A.

 

The first if block checks to see if there is a reference to the System Security Manager and creates if it does not exist.  The field name is used to hold the string that represents the name of the remote object.  The hostname is passed in via the args String array at index 0.

 

 

 

 

   public static void main(String[] args) {

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

         System.setSecurityManager(new RMISecurityManager());

      }

      String name = "//"+args[0]+"/com.gibsonsystems.tutorials.rmi.RemoteCmd";

      try{

         RemoteCmd r = new RemoteCmd();

         Naming.rebind(name,r);

         System.out.println("RemoteCmd object is bound");

      }catch (Exception e){        

         System.out.println("Error while binding RemoteCmd object");

         System.out.println(e.toString());

      }

   }

 

 

The last step is to bind the class to the RMI registry.  This is accomplished inside the try-catch block.  The RemoteCmd class is instantiated using the default constructor.  The new RemoteCmd object is then bound by calling the Naming.rebind method with the arguments of name and r.  If there is an exception thrown while trying to bind to the registry the catch block will print the stack trace to the console.  If the binding is successful the RemoteCmd object is bound message will be printed to the console.  Once the object is bound it is ready to receive invocations.

 

Security Notes

In order to connect to the registry first it must be running and the java.policy file must be edited to allow the connection.  For detailed information please see the following links.

 

 

RemoteCmdTestClient

The RemoteCmdTestClient is a command line Java application that is used to run remote commands from a command prompt.  The RemoteCmdTestClient consists of only the main method. The code snippet in figure 3 is the entire source for the RemoteCmdTestClient.

 

import java.rmi.*;

import com.gibsonsystems.rmi.RemoteCmdInterface;

public class RemoteCmdClient {

   public static void main(String[] args) {

      try {

         RemoteCmdInterface ri = (RemoteCmdInterface) Naming.lookup("//"+args[0]+"/com.hostname.rmi.RemoteCmd");

         String result = ri.runRemoteCommand(args[1]);

         System.out.println("The output of " + args[0] + " is " + result);

      }catch (Exception e){

         System.out.println("Error accessing remote object.");

         System.out.println(e.toString());

      }

   }

}
Appendix A

 

RemoteCmd

 

package com.gibsonsystems.rmi;

 

import java.rmi.*;

import java.rmi.server.*;

import java.lang.*;

import java.io.*;

public class RemoteCmd extends UnicastRemoteObject implements RemoteCmdInterface

{

 

   public RemoteCmd() throws RemoteException

   {

      super();

   }

 

   public String runRemoteCommand(String remoteCommand) throws RemoteException

   {

      String results = new String();

      try {

        Process child = Runtime.getRuntime().exec(remoteCommand);

        InputStream in = child.getInputStream();

        int c;

        while((c=in.read()) != -1) {

           //System.out.println("c="+c);

           Character cc = new Character((char)c);

           results = results + (new String(cc.toString()));

        }

        in.close();

      }catch(Exception e) {

        System.out.println("Caught exception: ");

        e.printStackTrace();

      }

     

      return results;

   }

 

   public static void main(String[] args)

   {

      if (System.getSecurityManager() == null)

      {

         System.setSecurityManager(new RMISecurityManager());

      }

 

      String name = "//localhost/com.hostname.rmi.RemoteCmd";

 

      try

      {

         RemoteCmd r = new RemoteCmd();

         Naming.rebind(name,r);

         System.out.println("RemoteCmd object is bound");

      }

      catch (Exception e)

      {

         System.out.println("Error while binding RemoteCmd object");

         System.out.println(e.toString());

      }

   }

}