Appearance
RMI Remote Invocation
Java's RMI (Remote Method Invocation) allows code in one JVM to invoke methods on another JVM over a network. RMI stands for Remote Method Invocation.
The party providing the service is called the server, while the party making the remote calls is called the client.
Let's implement a simple RMI example: the server will provide a WorldClock
service that allows clients to obtain the current time for a specified time zone by invoking the following method:
java
LocalDateTime getLocalDateTime(String zoneId);
Defining the Remote Interface
To implement RMI, both the server and the client must share the same interface. We define a WorldClock
interface as follows:
java
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.time.LocalDateTime;
public interface WorldClock extends Remote {
LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
}
Java's RMI requires that this interface extends java.rmi.Remote
, and each method must declare that it throws RemoteException
.
Implementing the Server
Next, we create the server-side implementation of the WorldClock
interface. The WorldClockService
class implements the remote method:
java
import java.rmi.RemoteException;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class WorldClockService implements WorldClock {
@Override
public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {
return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
}
}
With the service implementation complete, we need to use Java RMI's underlying support interfaces to expose this service over the network so that clients can invoke it remotely:
java
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class Server {
public static void main(String[] args) throws RemoteException {
System.out.println("Creating WorldClock remote service...");
// Instantiate the WorldClock service:
WorldClock worldClock = new WorldClockService();
// Export the service object to receive remote method calls:
WorldClock stub = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);
// Create or get the RMI registry on port 1099:
Registry registry = LocateRegistry.createRegistry(1099);
// Bind the stub to the name "WorldClock" in the registry:
registry.rebind("WorldClock", stub);
System.out.println("WorldClock service is ready.");
}
}
Explanation of Server Code:
- Instantiate the Service: Create an instance of
WorldClockService
. - Export the Service: Use
UnicastRemoteObject.exportObject
to export the service object, which returns a stub that clients will use to invoke remote methods. - Create Registry: Start an RMI registry on the default port
1099
. - Bind the Service: Bind the stub to the name
"WorldClock"
in the registry, making it available for clients to look up and invoke.
Implementing the Client
Next, we write the client code that will perform the RMI calls. Since RMI requires the client to have access to the same interface, ensure that the WorldClock.java
interface file is available to the client.
java
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.time.LocalDateTime;
public class Client {
public static void main(String[] args) throws RemoteException, NotBoundException {
// Connect to the RMI registry on localhost at port 1099:
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
// Look up the "WorldClock" service and cast it to WorldClock interface:
WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
// Invoke the remote method:
LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
// Print the result:
System.out.println("Current time in Asia/Shanghai: " + now);
}
}
Steps for the Client:
- Locate Registry: Connect to the RMI registry running on the server's host (
localhost
in this case) and port (1099
). - Lookup Service: Retrieve the remote object bound to the name
"WorldClock"
and cast it to theWorldClock
interface. - Invoke Remote Method: Call
getLocalDateTime
with the desiredzoneId
and process the result.
Understanding Stub and Skeleton
When running the client, since it only possesses the WorldClock
interface without the implementation class, the client obtains a proxy (stub) that represents the remote object. The stub handles the network communication, forwarding method calls to the server's skeleton, which in turn invokes the actual service implementation (WorldClockService
). The result is then serialized and sent back to the client.
Diagram of RMI Components:
┌─────────────┐ ┌───────────────┐ ┌───────────────┐
│ Client │ │ Stub │ │ Skeleton │
│ Application │◀──────▶│ (Proxy Object)│◀──────▶│ (Remote Server)│
└─────────────┘ └───────────────┘ └───────────────┘
▲ ▲ ▲
│ │ │
└──────────────────────┼─────────────────────────┘
│
Network (RMI)
Security Considerations
Java's RMI heavily relies on serialization and deserialization. This can lead to severe security vulnerabilities because Java's serialization involves not only data but also binary bytecode. Even with a whitelist mechanism, it is challenging to ensure that maliciously crafted bytecode is entirely excluded. Therefore, when using RMI:
- Trust Boundaries: Both the client and server should be within a trusted internal network.
- Port Exposure: Do not expose the RMI registry port (
1099
by default) to the public internet. - Alternative RPC Mechanisms: For cross-language compatibility and enhanced security, consider using more modern RPC frameworks like gRPC.
Exercise
Implement a remote invocation using RMI.
Summary
Java provides RMI to facilitate remote method invocation:
- Stub and Skeleton: RMI automatically generates stub (client-side proxy) and skeleton (server-side handler) to handle network communication.
- Shared Interface: Both client and server must share the same remote interface.
- Security: RMI's reliance on serialization necessitates that both parties trust each other. Avoid exposing RMI services to untrusted networks.
- Language Constraints: RMI is inherently Java-centric, making it difficult for other languages to interact. For cross-language RPC, consider alternatives like gRPC.