目錄
定義
RMI: 遠程方法調用(Remote Method Invocation),它支持存儲於不同地址空間的程序級對象之間彼此進行通信,實現遠程對象之間的無縫遠程調用。
Java RMI: 用於不同虛擬機之間的通信,這些虛擬機可以在不同的主機上、也可以在同一個主機上;一個虛擬機中的對象調用另一個虛擬上中的對象的方法,只不過是允許被遠程調用的對象要通過一些標誌加以標識
RMI遠程調用步驟
RMI的交互圖:
RMI由3個部分構成,第一個是rmiregistry(JDK提供的一個可以獨立運行的程序,在bin目錄下),第二個是server端的程序,對外提供遠程對象,第三個是client端的程序,想要調用遠程對象的方法。
首先,先啓動rmiregistry服務,啓動時可以指定服務監聽的端口,也可以使用默認的端口(1099)。
其次,server端在本地先實例化一個提供服務的實現類,然後通過RMI提供的Naming/Context/Registry(下面實例用的Registry)等類的bind或rebind方法將剛纔實例化好的實現類註冊到rmiregistry上並對外暴露一個名稱。
最後,client端通過本地的接口和一個已知的名稱(即rmiregistry暴露出的名稱)再使用RMI提供的Naming/Context/Registry等類的lookup方法從RMIService那拿到實現類。這樣雖然本地沒有這個類的實現類,但所有的方法都在接口裏了,便可以實現遠程調用對象的方法了。
存根和骨幹網的具體通信過程:
方法調用從客戶對象經存根(stub)、遠程引用層(Remote Reference Layer)和傳輸層(Transport Layer)向下,傳遞給主機,然後再次經傳輸層,向上穿過遠程調用層和骨幹網(Skeleton),到達服務器對象。
存根扮演着遠程服務器對象的代理的角色,使該對象可被客戶激活。
遠程引用層處理語義、管理單一或多重對象的通信,決定調用是應發往一個服務器還是多個。
傳輸層管理實際的連接,並且追蹤可以接受方法調用的遠程對象。
骨幹網完成對服務器對象實際的方法調用,並獲取返回值。
返回值向下經遠程引用層、服務器端的傳輸層傳遞迴客戶端,再向上經傳輸層和遠程調用層返回。最後,存根獲得返回值。
JAVA RMI簡單示例
本示例是client端調用server端遠程對象的加減法方法,具體步驟爲:
1. 定義一個遠程接口
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 必須繼承Remote接口。
* 所有參數和返回類型必須序列化(因爲要網絡傳輸)。
* 任意遠程對象都必須實現此接口。
* 只有遠程接口中指定的方法可以被調用。
*/
public interface IRemoteMath extends Remote {
// 所有方法必須拋出RemoteException
public double add(double a, double b) throws RemoteException;
public double subtract(double a, double b) throws RemoteException;
}
2. 遠程接口實現類
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import remote.IRemoteMath;
/**
* 服務器端實現遠程接口。
* 必須繼承UnicastRemoteObject,以允許JVM創建遠程的存根/代理。
*/
public class RemoteMath extends UnicastRemoteObject implements IRemoteMath {
private int numberOfComputations;
protected RemoteMath() throws RemoteException {
numberOfComputations = 0;
}
@Override
public double add(double a, double b) throws RemoteException {
numberOfComputations++;
System.out.println("Number of computations performed so far = "
+ numberOfComputations);
return (a+b);
}
@Override
public double subtract(double a, double b) throws RemoteException {
numberOfComputations++;
System.out.println("Number of computations performed so far = "
+ numberOfComputations);
return (a-b);
}
}
3. 服務器端
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import remote.IRemoteMath;
/**
* 創建RemoteMath類的實例並在rmiregistry中註冊。
*/
public class RMIServer {
public static void main(String[] args) {
try {
// 註冊遠程對象,向客戶端提供遠程對象服務。
// 遠程對象是在遠程服務上創建的,你無法確切地知道遠程服務器上的對象的名稱,
// 但是,將遠程對象註冊到RMI Registry之後,
// 客戶端就可以通過RMI Registry請求到該遠程服務對象的stub,
// 利用stub代理就可以訪問遠程服務對象了。
IRemoteMath remoteMath = new RemoteMath();
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
registry.bind("Compute", remoteMath);
System.out.println("Math server ready");
// 如果不想再讓該對象被繼續調用,使用下面一行
// UnicastRemoteObject.unexportObject(remoteMath, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 客戶端
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import remote.IRemoteMath;
public class MathClient {
public static void main(String[] args) {
try {
// 如果RMI Registry就在本地機器上,URL就是:rmi://localhost:1099/hello
// 否則,URL就是:rmi://RMIService_IP:1099/hello
Registry registry = LocateRegistry.getRegistry("localhost");
// 從Registry中檢索遠程對象的存根/代理
IRemoteMath remoteMath = (IRemoteMath)registry.lookup("Compute");
// 調用遠程對象的方法
double addResult = remoteMath.add(5.0, 3.0);
System.out.println("5.0 + 3.0 = " + addResult);
double subResult = remoteMath.subtract(5.0, 3.0);
System.out.println("5.0 - 3.0 = " + subResult);
}catch(Exception e) {
e.printStackTrace();
}
}
}
結果如下:
server端
client端