相關介紹: RMI全稱是Remote Method Invocation,即遠程方法調用。它是一種電腦之間利用遠程對象互相調用,從而實現雙方通訊的一種通訊機制。使用這種機制,某一臺電腦(虛擬機)上的對象可以調用另外一臺電腦(虛擬機)上的對象來獲取遠程數據。RMI是Enterpris ...
相關介紹:
RMI全稱是Remote Method Invocation,即遠程方法調用。它是一種電腦之間利用遠程對象互相調用,從而實現雙方通訊的一種通訊機制。使用這種機制,某一臺電腦(虛擬機)上的對象可以調用另外一臺電腦(虛擬機)上的對象來獲取遠程數據。RMI是Enterprise JavaBeans的支柱,是建立分散式Java應用程式的方便途徑。在過去,TCP/IP套接字通訊是遠程通訊的主要手段,但此開發方式沒有使用面向對象的方式實現開發,在開發一個如此的通訊機制時往往令程式員感覺到乏味,對此RPC(Remote Procedure Call)應運而生,它使程式員更容易地調用遠程程式,但在面對複雜的信息傳訊時,RPC依然未能很好的支持,而且RPC未能做到面向對象調用的開發模式。針對RPC服務遺留的問題,RMI出現在世人面前,它被設計成一種面向對象的通訊方式,允許程式員使用遠程對象來實現通信,並且支持多線程的服務。
要想實現基於RMI的應用程式,需要實現如下四個類:
遠程對象介面: 這個介面中包含了所有遠程方法的聲明。在實際使用中,客戶端只需要知道這個介面的存在,並不關心這個介面是如何實現的。它擴展了java.rmi.Remote介面,定義了遠程對象執行的輸出方法。由於在通過網路調用遠程方法的過程中,有很多意想不到的問題可能會引發錯誤,為此,該介面中的每個方法都必須定義為拋出一個java.rmi.RemoteException異常,它是許多更專門的RMI異常類的父類。
遠程對象介面實現: 這是遠程對象介面的具體實現。它包含了實現遠程介面的具體代碼,可以在這些遠程方法的實現代碼中加入一些輸出語句(或者log4j),以測試其被調用的情況。遠程對象介面的實現類必須繼承自java.rmi.server.UnicastRemoteObject類。它代表了遠程對象或者伺服器對象。與聲明遠程方法來拋出RemoteException對象不同,該遠程對象不需要做任何專門的事情來允許它的方法被遠程調用。UnicastRemoteObject和RMI其餘的基本結構將自動進行處理。
RMI伺服器端: RMI伺服器端生成遠程對象的一個實例,並用一個專用的URL對該對象進行註冊。在註冊時,首先要提供遠程對象所在伺服器的地址,同時需要對遠程對象進行標識,也就是給遠程對象一個名字。這個對象的名字在伺服器端和客戶端必須一致,這樣客戶端才能找到伺服器端的遠程對象。
伺服器地址(serverAddress)+遠程對象名字(objectName)構成了RMI的註冊URL。其具體形式為:
rmi://serverAddress:port/objectName
埠號可以自己定義,預設埠是1099。如果使用預設埠,在URL裡面可以不用給出。下麵是一個URL的例子:
rmi://192.168.1.123:2222/RemoteMethodRMI客戶端: RMI客戶端在遠程RMI伺服器端上查找服務對象,並將它轉換成本地介面類型,然後像對待一個本地對象一樣使用它。
除了上述介紹的4個RMI相關的類之外,還需要有一個相關的安全策略文件,如java.policy。由於Java內置的安全策略對遠程方法調用有一定的限制,所以我們必須編寫一個安全策略文件(這是一個文本文件,文件名可以隨便起)。在啟動伺服器端時把策略文件載入到虛擬機中,這樣伺服器端才能正常運行。
一個簡單的例子:
以下相關的代碼用於演示一個基於RMI的例子,客戶端傳入相關的參數,調用伺服器端對象的相關方法用來實現在客戶端輸出一段文字。
遠程對象介面:
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 用於演示遠程方法調用的實例
* @author 學徒
*此處定義了一個遠程方法調用的介面
*
*/
public interface RMethod extends Remote//該介面需要實現Remote介面
{
public abstract String sayHello(String name) throws RemoteException;//Remote介面下的所有遠程調用的方法都必須拋出RemoteException異常
}
遠程對象介面實現:
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 遠程對象介面的具體實現對象
* @author 學徒
*該實現對象必須繼承UnicastRemoteObject類,且實現定義的遠程對象的介面
*
*
*/
public class RMethodImpl extends UnicastRemoteObject implements RMethod
{
private static final long serialVersionUID = 1L;
protected RMethodImpl() throws RemoteException
{
super();
}
@Override
public synchronized String sayHello(String name) throws RemoteException
{
System.out.println("Client-" + name + ": invoking \" sayHello \"");//以上的執行會發生在伺服器端
return "Hello " + name + "\n\t this is a message from Remote Method";//該return的結果會返回給客戶端
}
}
配置信息文件類:
/**
* 該介面用於實現伺服器端和客戶端的共同配置,當遠程調用的某些信息發生改變時,只需要修改該介面的信息,而不需要去更改
* 代碼
* @author 學徒
*
*由於伺服器端需要註冊遠程對象調用的實例的URL,而且其格式為
*rmi://serverAddress:port/objectName
*
*為此在下麵配置相關的信息
*/
public interface Config
{
//對象實例的名字
String OBJECT_NAME = "RemoteMethod";
//對象伺服器的IP地址
String SERVER_IP = "192.168.199.241";
//對象伺服器進行監聽的埠號
int PORT = 1234;
}
RMI伺服器端:
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
/**
* 遠程對象調用的伺服器,該伺服器的作用是創建遠程對象實現的實例,並按照公用配置文件制定的對象名將這個實例註冊到伺服器端上。
* 當註冊成功之後,伺服器端程式就處於阻塞狀態,等待客戶端的遠程對象訪問請求。
*
* @author 學徒
*
*/
public class Server
{
public static void main(String[] args)
{
new Server();
}
public Server()
{
if (null == System.getSecurityManager())
{
System.setSecurityManager(new RMISecurityManager());
}
try
{
try
{
//用於註冊遠程對象調用的監聽埠
LocateRegistry.createRegistry(Config.PORT);
}
catch (java.rmi.server.ExportException ex)
{
System.out.println("Register the port failed:\n"+ ex.getMessage());
}
RMethod rm = new RMethodImpl();
String objAddr = "rmi://" + Config.SERVER_IP + ":" + Config.PORT+ "/" + Config.OBJECT_NAME;
//用於將該URL綁定到該對象實例上
java.rmi.Naming.rebind(objAddr, rm);
System.out.println("Server is running...");
}
catch (Exception e)
{
System.out.println("Server startup failed!");
e.printStackTrace();
}
}
}
RMI客戶端:
/**
* 在客戶端,其需要有和伺服器端同樣的兩個文件,一個是Config配置文件,因為其定義了RMI的配置信息,一個是
* Remote介面類,該類在客戶端可以通過遠程方法調用的方式生成一個使用的實例。 該類用於實現遠程對象調用的客戶端程式
* 客戶端通過調用伺服器端提供的遠程方法,來實現與伺服器的通信。這樣就不需要考慮具體的通信細節。
* 只要像使用本地方法一樣調用伺服器端的遠程方法,完成需要的功能。 要調用遠程方法,客戶端必須先構造一個RMI
* URL來找到遠程對象(註意在客戶端我們只使用遠程對象介面), 然後通過遠程對象來調用遠程方法。
*
* @author 學徒
*
*/
public class Client
{
private String name;
private String hostURL;
private String obj;
public Client(String name)
{
this.name = name;
hostURL = "rmi://" + Config.SERVER_IP + ":" + Config.PORT + "/";
this.obj = Config.OBJECT_NAME;
}
public void callRMethod()
{
try
{
RMethod rm = (RMethod) java.rmi.Naming.lookup(hostURL + obj);
String result = rm.sayHello(name);
System.out.println(result);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
Client c1 = new Client("Monica");
c1.callRMethod();
Client c2 = new Client("Joy");
c2.callRMethod();
Client c3 = new Client("Ross");
c3.callRMethod();
Client c4 = new Client("Chandler");
c4.callRMethod();
}
}
安全策略文件:
grant {
permission java.security.AllPermission;
};
ps:為方便說明該示例,文件的文件名取為java.policy
執行過程:
編寫了相關的java類之後,按照如下的方式創建訪問過程:
命令行模式
編譯:
$ javac *.java
運行伺服器端:
$ rmic RMethodImpl
$ java -Djava.security.policy=java.policy Server
Server is running...//此處是伺服器端啟動後的輸出
運行客戶端:
$ java Client
Hello Monica
this is a message from Remote Method
Hello Joy
this is a message from Remote Method
Hello Ross
this is a message from Remote Method
Hello Chandler
this is a message from Remote Method
伺服器端對應的輸出為:
$ java -Djava.security.policy=java.policy Server
Server is running...
Client-Monica: invoking " sayHello "
Client-Joy: invoking " sayHello "
Client-Ross: invoking " sayHello "
Client-Chandler: invoking " sayHello "
從輸出結果中可以看出,遠程方法的調用將其相關的方法的執行放在了伺服器端,而客戶端只需要傳遞相關的參數,以及接收相應的結果即可
伺服器相關的文件:
客戶端相關的文件:
參考博文: 學習筆記:JAVA RMI遠程方法調用簡單實例
推薦文章:深入理解rmi原理。在看這篇文章前,先瞭解如下兩個概念:1. stub和Skeleton:這兩個的身份是一致的,都是作為代理的存在。客戶端的稱作Stub,服務端的稱作Skeleton。要做到對程式員屏蔽遠程方法調用的細節,這兩個代理是必不可少的,包括網路連接等細節。2. Registry:顧名思義,可以認為Registry是一個“註冊所”,提供了服務名到服務的映射。