定義
RMI(Remote Method Invocation)爲遠程方法調用,是允許運行在一個Java虛擬機的對象調用運行在另一個Java虛擬機上的對象的方法。 這兩個虛擬機可以是運行在相同計算機上的不同進程中,也可以是運行在網絡上的不同計算機中。
Java RMI:Java遠程方法調用,即java RMI(Java Remote Method Invocation)是Java編程語言裏,一種用於實現遠程過程調用的應用程序編程接口。它使客戶機上運行的程序可以調用遠程服務器上的對象。遠程方法調用特性使Java編程人員能夠在網絡環境中分佈操作。RMI全部的宗旨就是儘可能簡化遠程接口對象的使用。
我們知道遠程過程調用(Remote Procedure Call, RPC)可以用於一個進程調用另一個進程(很可能在另一個遠程主機上)中的過程,從而提供了過程的分佈能力。Java 的 RMI 則在 RPC 的基礎上向前又邁進了一步,即提供分佈式對象間的通訊。
本地對象調用
我們先看看本地對象方法的調用:
- ObjectClass objectA = new ObjectClass();
- String retn = objectA.Method();
ObjectClass objectA = new ObjectClass();
String retn = objectA.Method();
但是想想,如果objectA對象在JVM a上;而我們的程序在JVM b上,而且想訪問JVM a上的objectA對象方法,如何做呢?對於JVM b上的應用程序來說,是不知道JVM a上創建的ObjectClass實例對象名稱是什麼,因爲這次我創建的實例對象可能是objectA,下次我程序一改,我創建的實例對象又叫objectB了,另外,我創沒創建ObjectClass實例對象,JVM b上應用程序又怎麼知道呢?
RMI就解決了這個問題。
工作原理
方法調用從客戶對象經佔位程序(Stub)、遠程引用層(Remote Reference Layer)和傳輸層(Transport Layer)向下,傳遞給主機,然後再次經傳 輸層,向上穿過遠程調用層和骨幹網(Skeleton),到達服務器對象。 佔位程序扮演着遠程服務器對象的代理的角色,使該對象可被客戶激活。 遠程引用層處理語義、管理單一或多重對象的通信,決定調用是應發往一個服務器還是多個。傳輸層管理實際的連接,並且追蹤可以接受方法調用的遠程對象。服務器端的骨幹網完成對服務器對象實際的方法調用,並獲取返回值。返回值向下經遠程引用層、服務器端的傳輸層傳遞迴客戶端,再向上經傳輸層和遠程調用層返回。最後,佔位程序獲得返回值。
要完成以上步驟需要有以下幾個步驟:
1、 生成一個遠程接口
2、 實現遠程對象(服務器端程序)
3、 生成佔位程序和骨幹網(服務器端程序)
4、 編寫服務器程序
5、 編寫客戶程序
6、 註冊遠程對象
7、 啓動遠程對象
由於有RMI系統的支持,我們寫RMI應用程序時只需要繼承相關類,實現相關接口就可以了。也就是說,我們只需要定義接口、接口實現、客戶端程序和服務端程序就可以了。
上圖中的stub和skeleton代理都是在服務端程序中由RMI系統動態生成,服務端程序只需要繼承java.rmi.server.UnicastRemoteObject類。
那麼上圖中的RMI Service(RMI registry)是怎麼回事呢?
先賣個關子:
可以說,RMI由3個部分構成,第一個是RMIService即JDK提供的一個可以獨立運行的程序(bin目錄下的rmiregistry),第二個是RMIServer即我們自己編寫的一個java項目,這個項目對外提供服務。第三個是RMIClient即我們自己編寫的另外一個java項目,這個項目遠程使用RMIServer提供的服務。
首先,RMIService必須先啓動並開始監聽對應的端口。
其次,RMIServer將自己提供的服務的實現類註冊到RMIService上,並指定一個訪問的路徑(或者說名稱)供RMIClient使用。
最後,RMIClient使用事先知道(或和RMIServer約定好)的路徑(或名稱)到RMIService上去尋找這個服務,並使用這個服務在本地的接口調用服務的具體方法。
通俗的講完了再稍微技術的講下:
首先,在一個JVM中啓動rmiregistry服務,啓動時可以指定服務監聽的端口,也可以使用默認的端口。
其次,RMIServer在本地先實例化一個提供服務的實現類,然後通過RMI提供的Naming,Context,Registry等類的bind或rebind方法將剛纔實例化好的實現類註冊到RMIService上並對外暴露一個名稱。
最後,RMIClient通過本地的接口和一個已知的名稱(即RMIServer暴露出的名稱)再使用RMI提供的Naming,Context,Registry等類的lookup方法從RMIService那拿到實現類。這樣雖然本地沒有這個類的實現類,但所有的方法都在接口裏了,想怎麼調就怎麼調吧。
值得注意的是理論上講RMIService,RMIServer,RMIClient可以部署到3個不同的JVM中,這個時候的執行的順序是RMIService—RMIServer—RMIClient。另外也可以由RMIServer來啓動RMIService這時候執行的順序是RMIServer—RMIService—RMIClient。
實際應用中很少有單獨提供一個RMIService服務器,開發的時候可以使用Registry類在RMIServer中啓動RMIService。
Java RMI 簡單示例
1. 定義一個遠程接口
- /* IHello.java */
- package mytest;
- /*
- * 在Java中,只要一個類extends了java.rmi.Remote接口,即可成爲存在於服務器端的遠程對象,
- * 供客戶端訪問並提供一定的服務。JavaDoc描述:Remote 接口用於標識其方法可以從非本地虛擬機上
- * 調用的接口。任何遠程對象都必須直接或間接實現此接口。只有在“遠程接口”
- * (擴展 java.rmi.Remote 的接口)中指定的這些方法纔可被遠程調用。
- */
- import java.rmi.Remote;
- public interface IHello extends Remote {
- /* extends了Remote接口的類或者其他接口中的方法若是聲明拋出了RemoteException異常,
- * 則表明該方法可被客戶端遠程訪問調用。
- */
- public String sayHello(String name) throws java.rmi.RemoteException;
- }
/* IHello.java */
package mytest;
/*
* 在Java中,只要一個類extends了java.rmi.Remote接口,即可成爲存在於服務器端的遠程對象,
* 供客戶端訪問並提供一定的服務。JavaDoc描述:Remote 接口用於標識其方法可以從非本地虛擬機上
* 調用的接口。任何遠程對象都必須直接或間接實現此接口。只有在“遠程接口”
* (擴展 java.rmi.Remote 的接口)中指定的這些方法纔可被遠程調用。
*/
import java.rmi.Remote;
public interface IHello extends Remote {
/* extends了Remote接口的類或者其他接口中的方法若是聲明拋出了RemoteException異常,
* 則表明該方法可被客戶端遠程訪問調用。
*/
public String sayHello(String name) throws java.rmi.RemoteException;
}
2. 遠程接口實現類
- /* HelloImpl.java */
- package mytest;
- import java.rmi.RemoteException;
- import java.rmi.server.UnicastRemoteObject;
- /*
- * 遠程對象必須實現java.rmi.server.UniCastRemoteObject類,這樣才能保證客戶端訪問獲得遠程對象時,
- * 該遠程對象將會把自身的一個拷貝以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱爲“存根”,
- * 而服務器端本身已存在的遠程對象則稱之爲“骨架”。其實此時的存根是客戶端的一個代理,用於與服務器端的通信,
- * 而骨架也可認爲是服務器端的一個代理,用於接收客戶端的請求之後調用遠程方法來響應客戶端的請求。
- */
- /* java.rmi.server.UnicastRemoteObject構造函數中將生成stub和skeleton */
- public class HelloImpl extends UnicastRemoteObject implements IHello {
- // 這個實現必須有一個顯式的構造函數,並且要拋出一個RemoteException異常
- protected HelloImpl() throws RemoteException {
- super();
- }
- private static final long serialVersionUID = 4077329331699640331L;
- public String sayHello(String name) throws RemoteException {
- return “Hello ” + name + “ ^_^ ”;
- }
- }
/* HelloImpl.java */
package mytest;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/*
* 遠程對象必須實現java.rmi.server.UniCastRemoteObject類,這樣才能保證客戶端訪問獲得遠程對象時,
* 該遠程對象將會把自身的一個拷貝以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱爲“存根”,
* 而服務器端本身已存在的遠程對象則稱之爲“骨架”。其實此時的存根是客戶端的一個代理,用於與服務器端的通信,
* 而骨架也可認爲是服務器端的一個代理,用於接收客戶端的請求之後調用遠程方法來響應客戶端的請求。
*/
/* java.rmi.server.UnicastRemoteObject構造函數中將生成stub和skeleton */
public class HelloImpl extends UnicastRemoteObject implements IHello {
// 這個實現必須有一個顯式的構造函數,並且要拋出一個RemoteException異常
protected HelloImpl() throws RemoteException {
super();
}
private static final long serialVersionUID = 4077329331699640331L;
public String sayHello(String name) throws RemoteException {
return "Hello " + name + " ^_^ ";
}
}
3. 服務端
- /* HelloServer.java */
- package mytest;
- import java.rmi.registry.LocateRegistry;
- import java.rmi.registry.Registry;
- /* 註冊遠程對象,向客戶端提供遠程對象服務
- * 遠程對象是在遠程服務上創建的,你無法確切地知道遠程服務器上的對象的名稱
- * 但是,將遠程對象註冊到RMI Service之後,客戶端就可以通過RMI Service請求
- * 到該遠程服務對象的stub了,利用stub代理就可以訪問遠程服務對象了
- */
- public class HelloServer {
- public static void main(String[] args) {
- try {
- IHello hello = new HelloImpl(); /* 生成stub和skeleton,並返回stub代理引用 */
- /* 本地創建並啓動RMI Service,被創建的Registry服務將在指定的端口上偵聽到來的請求
- * 實際上,RMI Service本身也是一個RMI應用,我們也可以從遠端獲取Registry:
- * public interface Registry extends Remote;
- * public static Registry getRegistry(String host, int port) throws RemoteException;
- */
- LocateRegistry.createRegistry(1099);
- /* 將stub代理綁定到Registry服務的URL上 */
- java.rmi.Naming.rebind(”rmi://localhost:1099/hello”, hello);
- System.out.print(”Ready”);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
/* HelloServer.java */
package mytest;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/* 註冊遠程對象,向客戶端提供遠程對象服務
* 遠程對象是在遠程服務上創建的,你無法確切地知道遠程服務器上的對象的名稱
* 但是,將遠程對象註冊到RMI Service之後,客戶端就可以通過RMI Service請求
* 到該遠程服務對象的stub了,利用stub代理就可以訪問遠程服務對象了
*/
public class HelloServer {
public static void main(String[] args) {
try {
IHello hello = new HelloImpl(); /* 生成stub和skeleton,並返回stub代理引用 */
/* 本地創建並啓動RMI Service,被創建的Registry服務將在指定的端口上偵聽到來的請求
* 實際上,RMI Service本身也是一個RMI應用,我們也可以從遠端獲取Registry:
* public interface Registry extends Remote;
* public static Registry getRegistry(String host, int port) throws RemoteException;
*/
LocateRegistry.createRegistry(1099);
/* 將stub代理綁定到Registry服務的URL上 */
java.rmi.Naming.rebind("rmi://localhost:1099/hello", hello);
System.out.print("Ready");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 客戶端
- /* Hello_RMI_Client.java */
- package mytest;
- import java.rmi.Naming;
- /* 客戶端向服務端請求遠程對象服務 */
- public class Hello_RMI_Client {
- public static void main(String[] args) {
- try {
- /* 從RMI Registry中請求stub
- * 如果RMI Service就在本地機器上,URL就是:rmi://localhost:1099/hello
- * 否則,URL就是:rmi://RMIService_IP:1099/hello
- */
- IHello hello = (IHello) Naming.lookup(”rmi://localhost:1099/hello”);
- /* 通過stub調用遠程接口實現 */
- System.out.println(hello.sayHello(”zhangxianxin”));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
/* Hello_RMI_Client.java */
package mytest;
import java.rmi.Naming;
/* 客戶端向服務端請求遠程對象服務 */
public class Hello_RMI_Client {
public static void main(String[] args) {
try {
/* 從RMI Registry中請求stub
* 如果RMI Service就在本地機器上,URL就是:rmi://localhost:1099/hello
* 否則,URL就是:rmi://RMIService_IP:1099/hello
*/
IHello hello = (IHello) Naming.lookup("rmi://localhost:1099/hello");
/* 通過stub調用遠程接口實現 */
System.out.println(hello.sayHello("zhangxianxin"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
RMI 應用各個類的交互時序圖
RMI應用需要用到的類
實現一個stub和skeleton程序
- public interface Person {
- public int getAge() throws Throwable;
- public String getName() throws Throwable;
- }
public interface Person {
public int getAge() throws Throwable;
public String getName() throws Throwable;
}
Person_Stub代碼:
- import java.io.ObjectOutputStream;
- import java.io.ObjectInputStream;
- import java.net.Socket;
- public class Person_Stub implements Person {
- private Socket socket;
- public Person_Stub() throws Throwable {
- // connect to skeleton
- socket = new Socket(“computer_name”, 9000);
- }
- public int getAge() throws Throwable {
- // pass method name to skeleton
- ObjectOutputStream outStream =
- new ObjectOutputStream(socket.getOutputStream());
- outStream.writeObject(”age”);
- outStream.flush();
- ObjectInputStream inStream =
- new ObjectInputStream(socket.getInputStream());
- return inStream.readInt();
- }
- public String getName() throws Throwable {
- // pass method name to skeleton
- ObjectOutputStream outStream =
- new ObjectOutputStream(socket.getOutputStream());
- outStream.writeObject(”name”);
- outStream.flush();
- ObjectInputStream inStream =
- new ObjectInputStream(socket.getInputStream());
- return (String)inStream.readObject();
- }
- }
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
public class Person_Stub implements Person {
private Socket socket;
public Person_Stub() throws Throwable {
// connect to skeleton
socket = new Socket("computer_name", 9000);
}
public int getAge() throws Throwable {
// pass method name to skeleton
ObjectOutputStream outStream =
new ObjectOutputStream(socket.getOutputStream());
outStream.writeObject("age");
outStream.flush();
ObjectInputStream inStream =
new ObjectInputStream(socket.getInputStream());
return inStream.readInt();
}
public String getName() throws Throwable {
// pass method name to skeleton
ObjectOutputStream outStream =
new ObjectOutputStream(socket.getOutputStream());
outStream.writeObject("name");
outStream.flush();
ObjectInputStream inStream =
new ObjectInputStream(socket.getInputStream());
return (String)inStream.readObject();
}
}
- import java.io.ObjectOutputStream;
- import java.io.ObjectInputStream;
- import java.net.Socket;
- import java.net.ServerSocket;
- public class Person_Skeleton extends Thread {
- private PersonServer myServer;
- public Person_Skeleton(PersonServer server) {
- // get reference of object server
- this.myServer = server;
- }
- public void run() {
- try {
- // new socket at port 9000
- ServerSocket serverSocket = new ServerSocket(9000);
- // accept stub’s request
- Socket socket = serverSocket.accept();
- while (socket != null) {
- // get stub’s request
- ObjectInputStream inStream =
- new ObjectInputStream(socket.getInputStream());
- String method = (String)inStream.readObject();
- // check method name
- if (method.equals(“age”)) {
- // execute object server’s business method
- int age = myServer.getAge();
- ObjectOutputStream outStream =
- new ObjectOutputStream(socket.getOutputStream());
- // return result to stub
- outStream.writeInt(age);
- outStream.flush();
- }
- if(method.equals(“name”)) {
- // execute object server’s business method
- String name = myServer.getName();
- ObjectOutputStream outStream =
- new ObjectOutputStream(socket.getOutputStream());
- // return result to stub
- outStream.writeObject(name);
- outStream.flush();
- }
- }
- } catch(Throwable t) {
- t.printStackTrace();
- System.exit(0);
- }
- }
- }
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.net.ServerSocket;
public class Person_Skeleton extends Thread {
private PersonServer myServer;
public Person_Skeleton(PersonServer server) {
// get reference of object server
this.myServer = server;
}
public void run() {
try {
// new socket at port 9000
ServerSocket serverSocket = new ServerSocket(9000);
// accept stub's request
Socket socket = serverSocket.accept();
while (socket != null) {
// get stub's request
ObjectInputStream inStream =
new ObjectInputStream(socket.getInputStream());
String method = (String)inStream.readObject();
// check method name
if (method.equals("age")) {
// execute object server's business method
int age = myServer.getAge();
ObjectOutputStream outStream =
new ObjectOutputStream(socket.getOutputStream());
// return result to stub
outStream.writeInt(age);
outStream.flush();
}
if(method.equals("name")) {
// execute object server's business method
String name = myServer.getName();
ObjectOutputStream outStream =
new ObjectOutputStream(socket.getOutputStream());
// return result to stub
outStream.writeObject(name);
outStream.flush();
}
}
} catch(Throwable t) {
t.printStackTrace();
System.exit(0);
}
}
}
Skeleton類 extends from Thread,它長駐在後臺運行,隨時接收client發過來的request。並根據發送過來的key去調用相應的business method。
- public class PersonServer implements Person {
- private int age;
- private String name;
- public PersonServer(String name, int age) {
- this.age = age;
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public String getName() {
- return name;
- }
- public static void main(String args []) {
- // new object server
- PersonServer person = new PersonServer(“Richard”, 34);
- Person_Skeleton skel = new Person_Skeleton(person);
- skel.start();
- }
- }
public class PersonServer implements Person {
private int age;
private String name;
public PersonServer(String name, int age) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public static void main(String args []) {
// new object server
PersonServer person = new PersonServer("Richard", 34);
Person_Skeleton skel = new Person_Skeleton(person);
skel.start();
}
}
- public class PersonClient {
- public static void main(String [] args) {
- try {
- Person person = new Person_Stub();
- int age = person.getAge();
- String name = person.getName();
- System.out.println(name + ” is ” + age + “ years old”);
- } catch(Throwable t) {
- t.printStackTrace();
- }
- }
- }
public class PersonClient {
public static void main(String [] args) {
try {
Person person = new Person_Stub();
int age = person.getAge();
String name = person.getName();
System.out.println(name + " is " + age + " years old");
} catch(Throwable t) {
t.printStackTrace();
}
}
}
Client(PersonClient)的本質是,它要知道Person接口的定義,並實例一個Person_Stub,通過Stub來調用business method,至於Stub怎麼去和Server溝通,Client就不用管了。
Person person = new Person_Stub();而不是Person_Stub person = new Person_Stub();爲什麼?因爲要面向接口編程嘛,呵呵。
參考
1. http://www.blogjava.NET/zhenyu33154/articles/320245.html
2. http://guojuanjun.blog.51cto.com/277646/1423392/
3. http://blog.sina.com.cn/s/blog_4918a7d90100oftg.html
4. http://haolloyin.blog.51cto.com/1177454/332426/
5. http://www.it165.net/pro/html/201404/11411.html
6. http://spidermanzy.iteye.com/blog/1741045
7. http://www.infoq.com/cn/articles/cf-java-object-serialization-rmi/
8. http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html