Java RMI遠端方法呼叫詳解

pan_jinquan發表於2016-07-22

Java RMI遠端方法呼叫詳解

    【尊重原創,轉載請註明出處】http://blog.csdn.net/guyuealian/article/details/51992182
一、Java RMI機制: 
       遠端方法呼叫RMI(Remote Method Invocation),是允許執行在一個Java虛擬機器的物件呼叫執行在另一個Java虛擬機器上的物件的方法。 這兩個虛擬機器可以是執行在相同計算機上的不同程式中,也可以是執行在網路上的不同計算機中。
       Java RMI:Java遠端方法呼叫,即Java RMI(Java Remote Method Invocation)是Java程式語言裡,一種用於實現遠端過程呼叫的應用程式程式設計介面。它使客戶機上執行的程式可以呼叫遠端伺服器上的物件。遠端方法呼叫特性使Java程式設計人員能夠在網路環境中分佈操作。RMI全部的宗旨就是儘可能簡化遠端介面物件的使用。
       而RPC是遠端過程呼叫(Remote Procedure Call)可以用於一個程式呼叫另一個程式(很可能在另一個遠端主機上)中的過程,從而提供了過程的分佈能力。Java 的 RMI 則在 RPC 的基礎上向前又邁進了一步,即提供分散式物件間的通訊
 (1)RMI框架
       【參考資料】
        《Java網路程式設計精解》孫衛琴,這本書適合入門學習RMI框架基礎   
         http://wenku.baidu.com/view/90171bd03186bceb19e8bbdc.html?from=search
       RMI框架封裝了所有底層通訊細節,並且解決了編組、分散式垃圾收集、安全檢查和併發性等通用問題。有了現成的框架,開發人員就只需專注於開發與特定問題領域相關的各種本地物件和遠端物件。

      要了解RMI原理,先了解一下Stub和Skeleton兩個概念。
(2)Stub和Skeleton
      RMI框架採用代理來負責客戶與遠端物件之間通過Socket進行通訊的細節。RMI框架為遠端物件分別生成了客戶端代理和伺服器端代理。位於客戶端的代理類稱為存根(Stub),位於伺服器端的代理類稱為骨架(Skeleton)。


    【相關資料
     《RMI(Remote Method Invocation)初窺門徑》 http://blog.csdn.net/smcwwh/article/details/7080997 
     stub(存根)和skeleton(骨架)在RMI中充當代理角色,在現實開發中主要是用來隱藏系統和網路的的差異, 這一部分的功能在RMI開發中對程式設計師是透明的。Stub為客戶端編碼遠端命令並把他們傳送到伺服器。而Skeleton則是把遠端命令解碼,呼叫服務端的遠端物件的方法,把結果在編碼發給stub,然後stub再解碼返回撥用結果給客戶端。
     RMI遠端過程呼叫的實現過程如下圖所示:

     RMI 框架的基本原理大概如下圖,應用了代理模式來封裝了本地存根與真實的遠端物件進行通訊的細節

參考資料
    《Java RMI 框架(遠端方法呼叫)》http://haolloyin.blog.51cto.com/1177454/332426  
    《 java RMI原理詳解》 http://blog.csdn.net/xinghun_4/article/details/45787549

二、Java RMI 簡單示例

    別急,慢慢分析~具體程式碼在下面,附例子程式碼下載:http://download.csdn.net/detail/guyuealian/9583633
大致說來,建立一個RMI應用包括以下步驟:
      (1)建立遠端介面:繼承java.rmi.Remote介面。
      (2)建立遠端類:實現遠端介面。
      (3)建立伺服器程式:建立遠端物件,通過createRegistry()方法註冊遠端物件。並通過bind或者rebind方法,把遠端物件繫結到指定名稱空間(URL)中。
      (4)建立客戶程式:通過 lookup()方法查詢遠端物件,進行遠端方法呼叫 
     下面具體分析每個步驟:
(1)建立遠端介面:繼承java.rmi.Remote介面。
       遠端介面中宣告瞭可以被客戶程式訪問的遠端方法。RMI規範要求遠端物件所屬的類實現一個遠端介面,並且遠端介面符合以下條件:
       (a)直接或間接繼承java.rmi.Remote介面。
       (b)介面中的所有方法宣告丟擲java.rmi.RemoteException。
(2)建立遠端類:實現遠端介面。
        遠端類就是遠端物件所屬的類。RMI規範要求遠端類必須實現一個遠端介面。此外,為了使遠端類的例項變成能為遠端客戶提供服務的遠端物件,可通過以下兩種途徑之一把它匯出(export)為遠端物件
       (a)匯出為遠端物件的第一種方式:使遠端類實現遠端介面時,同時繼承java.rmi.server.UnicastRemoteObject類,並且遠端類的構造方法必須宣告丟擲RemoteException。這是最常用的方式,下面的本例子就採取這種方式。
public class RemoteImpl extends UnicastRemoteObject implements RemoteInterface
       (b)匯出為遠端物件的第二種方式:如果一個遠端類已經繼承了其他類,無法再繼承UnicastRemoteObject類,那麼可以在構造方法中呼叫UnicastRemoteObject類的靜態exportObject()方法,同樣,遠端類的構造方法也必須宣告丟擲RemoteException。
public class RemoteImpl extends OtherClass implements RemoteInterface{
  private String name;
  public RemoteImpl (String name)throws RemoteException{
    this.name=name;
    UnicastRemoteObject.exportObject(this,0);
  }
        在構造方法RemoteImpl 中呼叫了UnicastRemoteObject.exportObject(this,0)方法,將自身匯出為遠端物件。
        exportObject()是UnicastRemoteObject的靜態方法,原始碼是:
    protected UnicastRemoteObject(int port) throws RemoteException
    {
        this.port = port;
        exportObject((Remote) this, port);
    }
    public static Remote exportObject(Remote obj, int port)
        throws RemoteException
    {
        return exportObject(obj, new UnicastServerRef(port));
    }
       exportObject(Remote obj, int port),該方法負責把引數obj指定的物件匯出為遠端物件,使它具有相應的存根(Stub),並監聽遠端客戶的方法呼叫請求;引數port指導監聽的埠,如果值為0,表示監聽任意一個匿名埠。
(3)建立伺服器程式:建立遠端物件,在rmiregistry登錄檔中註冊遠端物件,並繫結到指定的URL中。
       RMI採用一種命名服務機制來使得客戶程式可以找到伺服器上的一個遠端物件。在JDK的安裝目錄的bin子目錄下有一個rmiregistry.exe程式,它是提供命名服務的登錄檔程式。
      伺服器程式的一大任務就是向rmiregistry登錄檔註冊遠端物件。從JDK1.3以上版本開始,RMI的命名服務API被整合到JNDI(Java Naming and Directory Interface,Java名字與目錄介面)中。在JNDI中,javax.naming.Context介面宣告瞭註冊、查詢,以及登出物件的方法:
    【1】 bind(String name,Object obj):註冊物件,把物件與一個名字name繫結
,這裡的name其實就是URL格式。如果該名字已經與其它物件繫結,就會丟擲NameAlreadyBoundException。
    【2】rebind(String name,Object obj):註冊物件,把物件與一個名字繫結。如果該名字已經與其它物件繫結,不會丟擲NameAlreadyBoundException,而是把當前引數obj指定的物件覆蓋原先的物件。
    【3】 lookup(String name):查詢物件,返回與引數name指定的名字所繫結的物件。
    【4】unbind(String name):登出物件,取消物件與名字的繫結。
     註冊一個遠端物件remoteObj2 關鍵程式碼如下:
RemoteInterface remoteObj2 = new RemoteImpl();// 建立遠端物件
Context namingContext = new InitialContext();// 初始化命名內容
LocateRegistry.createRegistry(8892);// 在本地主機上建立和匯出登錄檔例項,並在指定的埠上接受請求
namingContext.rebind("rmi://localhost:8892/RemoteObj2", remoteObj2);// 註冊物件,即把物件與一個名字繫結。
     但在JDK1.3版本或更低的版本,需要使用java.rmi.Naming來註冊遠端物件,註冊程式碼代替如下:
RemoteInterface remoteObj = new RemoteImpl();
LocateRegistry.createRegistry(8891);
Naming.rebind("rmi://localhost:8891/RemoteObj", remoteObj);
(4)建立客戶程式:通過 lookup()方法查詢遠端物件,進行遠端方法呼叫
關鍵程式碼如下:
Context namingContext = new InitialContext();// 初始化命名內容
RemoteInterface RmObj2 = (RemoteInterface) namingContext.lookup("rmi://localhost:8892/RemoteObj2");//獲得遠端物件的存根物件
System.out.println(RmObj2.doSomething());//通過遠端物件,呼叫doSomething方法
     在JDK1.3版本或更低的版本,如下方式呼叫;
RemoteInterface RmObj = (RemoteInterface) Naming.lookup("rmi://localhost:8891/RemoteObj");
System.out.println(RmObj.doSomething());
例子具體程式碼:
   1. 建立遠端介面:繼承java.rmi.Remote介面。
package rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
//宣告一個遠端介面RemoteInterface,該介面必須繼承Remote介面
//介面中需要被遠端呼叫的方法,必須丟擲RemoteException異常
public interface RemoteInterface extends Remote {
	// 宣告一個doSomething方法
	public String doSomething() throws RemoteException;
	// 宣告一個計算方法Calculate
	public int Calculate(int num1, int num2) throws RemoteException;
}
       在Java中,只要一個類extends了java.rmi.Remote介面,即可成為存在於伺服器端的遠端物件, 供客戶端訪問並提供一定的服務。JavaDoc描述:Remote 介面用於標識其方法可以從非本地虛擬機器上呼叫的介面。任何遠端物件都必須直接或間接實現此介面。只有在“遠端介面”  (擴充套件 java.rmi.Remote 的介面)中指定的這些方法才可被遠端呼叫。注意:介面中需要被遠端呼叫的方法,必須丟擲RemoteException異常。
      2. 建立遠端類:實現遠端介面
package rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
//實現遠端介面RemoteInterface,並繼承UnicastRemoteObject
//注意RemoteObject這個類,實現了Serializable, Remote這兩個介面
public class RemoteImpl extends UnicastRemoteObject implements RemoteInterface {
	// 這個實現必須有一個顯式的建構函式,並且要丟擲一個RemoteException異常
	public RemoteImpl() throws RemoteException {
	}
	// 實現doSomething方法
	public String doSomething() throws RemoteException {
		return "OK ,You can do......";
	}
	// 實現Calculate方法,返回計算結果
	public int Calculate(int num1, int num2) throws RemoteException {
		return (num1 + num2);
	}
}
      遠端物件必須實現java.rmi.server.UniCastRemoteObject類,該類的建構函式中將生成stub和skeleton, 這樣才能保證客戶端訪問獲得遠端物件時,該遠端物件將會把自身的一個拷貝以Socket的形式傳輸給客戶端,此時客戶端所獲得的這個拷貝稱為Stub(存根), 而伺服器端本身已存在的遠端物件則稱之為Skeleton(骨架)其實此時的存根是客戶端的一個代理,用於與伺服器端的通訊,  而骨架也可認為是伺服器端的一個代理,用於接收客戶端的請求之後呼叫遠端方法來響應客戶端的請求。
     3.建立伺服器程式:在rmiregistry登錄檔中註冊遠端物件,向客戶端提供遠端物件服務  
package rmi2;
import javax.naming.Context;
import javax.naming.InitialContext;

import rmi.RemoteInterface;

public class ClientTest {
	public static void main(String args[]) {
		try {
			Context namingContext = new InitialContext();// 初始化命名內容
			RemoteInterface RmObj2 = (RemoteInterface) namingContext
					.lookup("rmi://localhost:8892/RemoteObj2");//獲得遠端物件的存根物件
			System.out.println(RmObj2.doSomething());//通過遠端物件,呼叫doSomething方法
			System.out.println("遠端伺服器計算結果為:" + RmObj2.Calculate(90, 2));
		} catch (Exception e) {
		}
	}
}
   在JDK1.3版本或更低的版本,如下方式註冊;
package rmi;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
//在JDK1.3版本或更低的版本,需要使用java.rmi.Naming來註冊遠端物件
//建立RMI登錄檔,啟動RMI服務,並將遠端物件註冊到RMI登錄檔中。
public class RMIServer {
	public static void main(String args[])
			throws java.rmi.AlreadyBoundException {

		try {
			// 建立一個遠端物件RemoteObj,實質上隱含了是生成stub和skeleton,並返回stub代理引用
			RemoteInterface remoteObj = new RemoteImpl();

			// 本地建立並啟動RMI Service,被建立的Registry服務將在指定的埠,偵聽請求
			// Java預設埠是1099,缺少登錄檔建立,則無法繫結物件到遠端登錄檔上
			LocateRegistry.createRegistry(8891);

			// 把遠端物件註冊到RMI註冊伺服器上,並命名為RemoteObj(名字可自定義,客戶端要對應)
			// 繫結的URL標準格式為:rmi://host:port/name(其中協議名可以省略,下面兩種寫法都是正確的)
			Naming.rebind("rmi://localhost:8891/RemoteObj", remoteObj);// 將stub代理繫結到Registry服務的URL上
			// Naming.bind("//localhost:8880/RemoteObj",remoteObj);

			System.out.println(">>>>>INFO:遠端IHello物件繫結成功!");
		} catch (RemoteException e) {
			System.out.println("建立遠端物件發生異常!");
			e.printStackTrace();
		} catch (MalformedURLException e) {
			System.out.println("發生URL畸形異常!");
			e.printStackTrace();
		}
	}
}
     RMIServer類主要實現註冊遠端物件,並向客戶端提供遠端物件服務。遠端物件是在遠端服務上建立的,你無法確切地知道遠端伺服器上的物件的名稱 。但是,將遠端物件註冊到RMI Service之後,客戶端就可以通過RMI Service請求到該遠端服務物件的stub了,利用stub代理就可以訪問遠端服務物件了 。
      4. 客戶端程式碼
     Server端的程式碼已經全部寫完,這時把伺服器的介面RemoteInterface打包成jar,以便在Client端的專案使用。
     專案-->右鍵-->匯出-->jar->選擇RemoteInterface.java-->finish;關於客戶端如何匯入jar包,請看這裡:http://jingyan.baidu.com/article/ca41422fc76c4a1eae99ed9f.html

package rmi2;
import javax.naming.Context;
import javax.naming.InitialContext;

import rmi.RemoteInterface;
public class ClientTest {
	public static void main(String args[]) {
		try {
			Context namingContext = new InitialContext();// 初始化命名內容
			RemoteInterface RmObj2 = (RemoteInterface) namingContext
					.lookup("rmi://localhost:8892/RemoteObj2");//獲得遠端物件的存根物件
			System.out.println(RmObj2.doSomething());//通過遠端物件,呼叫doSomething方法
			System.out.println("遠端伺服器計算結果為:" + RmObj2.Calculate(90, 2));
		} catch (Exception e) {
		}
	}
}
在JDK1.3版本或更低的版本,如下方式呼叫
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class ClientTest {
	public static void main(String args[]) {
		try {
			// 在RMI服務登錄檔中查詢名稱為RemoteObj的物件,並呼叫其上的方法
			// 客戶端通過命名服務Naming獲得指向遠端物件的遠端引用
			RemoteInterface RmObj = (RemoteInterface) Naming
					.lookup("rmi://localhost:8881/RemoteObj");
			System.out.println(RmObj.doSomething());
			System.out.println("遠端伺服器計算結果為:" + RmObj.Calculate(1, 2));
		} catch (NotBoundException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
}
輸出結果:
OK ,You can do......
遠端伺服器計算結果為:92
例子程式碼下載:http://download.csdn.net/detail/guyuealian/9583633




如果你覺得該帖子幫到你,還望貴人多多支援,鄙人會再接再厲,繼續努力的~

相關文章