Hi! Today we'll consider a rather interesting topic: Java RMI. This stands for Remote Method Invocation.
You can use RMI to allow two programs to communicate with each other, even if they are on different computers. Does that sound cool? :)
And it's not so difficult to do! In today's lesson, we'll analyze the elements of the RMI interaction and figure out how to configure it.
The first thing we need is a client and a server. We don't really need to dive deep into computer terminology. When it comes to RMI, these are just two programs. One of them will include an object, and the other will call methods on that object.
Calling methods of an object that exists in a different program — now that's something we haven't done yet! It's time to give it a try! :)
To avoid getting bogged down, let's keep our program simple.
In general, a server performs some calculations that requested by a client. And so it will be with us.
Our server will be a simple calculator program. It will have only one method: multiply(). It will multiply two numbers sent to it by the the client program, and then return the result.
First of all, we need an interface:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Calculator extends Remote {
int multiply(int x, int y) throws RemoteException;
}
Why do we need an interface?
Because RMI relies on creating proxies, which you studied in past lessons. As you probably remember, we work with proxies through interfaces, not classes. There are 2 important requirements for our interface!
- It must extend the Remote interface.
- All its methods must throw a RemoteException (the IDE won't do this automatically — you need to add this manually!).
import java.rmi.RemoteException;
public class RemoteCalculationServer implements Calculator {
@Override
public int multiply(int x, int y) throws RemoteException {
return x*y;
}
}
There's not really anything to comment on here :)
Now we need to write a server program that will configure and run our calculator object.
It will look like this:
import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class ServerMain {
public static final String UNIQUE_BINDING_NAME = "server.calculator";
public static void main(String[] args) throws RemoteException, AlreadyBoundException, InterruptedException {
final RemoteCalculationServer server = new RemoteCalculationServer();
final Registry registry = LocateRegistry.createRegistry(2732);
Remote stub = UnicastRemoteObject.exportObject(server, 0);
registry.bind(UNIQUE_BINDING_NAME, stub);
Thread.sleep(Integer.MAX_VALUE);
}
}
Let's figure this out :)
In the first line, we declare some String variable:
public static final String UNIQUE_BINDING_NAME = "server.calculator";
This string is the remote object's unique name. Our client program uses this name to find our server: you will see this later.
Next, we create our calculator object:
final RemoteCalculationServer server = new RemoteCalculationServer();
Everything is clear here. What comes next is more interesting:
final Registry registry = LocateRegistry.createRegistry(2732);
This Registry object is a registry of remote objects. These are objects that other programs can access remotely :)
We passed the number 2732 to the LocateRegistry.createRegistry() method. This is the port number — a unique number that other programs will use to find our object registry (again, you will see this below). Moving right along... Let's see what happens in the next line:
Remote stub = UnicastRemoteObject.exportObject(server, 0);
We create a stub in this line.
A stub encapsulates the entire remote call. You can consider this the most important element of RMI.
What does it do?
- It receives all the information about a remote call of some method.
- If the method has parameters, the stub will deserialize them. Pay attention to this point! The arguments that you pass to the remotely called methods must be serializable (after all, they will be transmitted over the network). This is no problem for us — we're just transmitting numbers. But if you're transmitting objects, don't forget this requirement!
- After that, the stub calls the desired method.
registry.bind(UNIQUE_BINDING_NAME, stub);
We "register" our stub in the remote object registry under the name we made up at the very beginning. Now the client will be able to find it!
Perhaps you noticed that we put the program's main thread to sleep at the end:
Thread.sleep(Integer.MAX_VALUE);
We just need the server to run for a long time. In the IDE, we will simultaneously launch two main() methods: first, the server's main() method (in the ServerMain class, which we have already written), and second, the client's main() method (in the ClientMain class, which we will write below). It's important that the server program is not terminated while we start the client, so we just put it to sleep for a long time. In any event, it will keep running :)
Now we can run our server's main() method. Let it run and wait for the client program to call some method :)
Now let's write the client program! It will send numbers to our server for multiplication.
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class ClientMain {
public static final String UNIQUE_BINDING_NAME = "server.calculator";
public static void main(String[] args) throws RemoteException, NotBoundException {
final Registry registry = LocateRegistry.getRegistry(2732);
Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);
int multiplyResult = calculator.multiply(20, 30);
System.out.println(multiplyResult);
}
}
It looks simple. But what's going on here?
First, the client must know the unique name of the object whose methods it will call remotely. Accordingly, in the client program, we created the public static final String UNIQUE_BINDING_NAME = "server.calculator"; variable.
Next, in the main() method, we get access to the register of remote objects.
To do this, we need to call the LocateRegistry.getRegistry() method and pass the port number used to create our registry in the ServerMain program (port 2732; this number is just an example — you can try using a different number):
final Registry registry = LocateRegistry.getRegistry(2732);
Now we just need to get the desired object from the registry! This is easy, because we know its unique name!
Calculator calculator = (Calculator) registry.lookup(UNIQUE_BINDING_NAME);
Pay attention to type casting. We cast the received object to the Calculator interface, not to the RemoteCalculationServer class. As we said at the beginning of the lesson, RMI relies on a proxy, so remote calls are available only for the methods of an interface, not the methods of a classes.
Finally, we remotely call the multiply() method on our object and output the result to the console.
int multiplyResult = calculator.multiply(20, 30);
System.out.println(multiplyResult);
The ServerMain class's main() method has already been running for a long time. Now it's time to run the main() method in the client program (ClientMain)!
Console output:
600
That's it! Our program (two programs, actually!) did what it was supposed to do :)
If you have the time and desire, you can spice this up a little. For example, make the calculator support the four standard arithmetic operations, and pass numbers not as primitive types, but as CalculationInstance(int x, int y) objects.
See you in the next lesson! :)
GO TO FULL VERSION