天天看點

【初學】RMI(Remote Method Invocation)初窺門徑

【引言】

作為J2EE核心技術之一的RMI,它允許客服端調用一個遠端伺服器的元件,并傳回調用結果(傳回值或異常),可以完成分布式應用。整個調用過程由RMI實作,對使用者透明。

【Stub和Skeleton】

1、要了解RMI原理,Stub和Skeleton是必須先了解的兩個概念。

以下引用别的一段話:

做個比方說明這兩個概念。 假如你是A,你想借D的工具,但是又不認識D的管家C,是以你找來B來幫你,B認識C。B在這時就是一個代理,代理你的請求,依靠自己的話語去借。C呢他負責D家東西收回和借出 ,但是要有D的準許。在得到D的準許以後,C再把東西給B,B呢再轉給A。stub和skeleton在RMI中就是角色就是B和C,他們都是代理角色,在現實開發中隐藏系統和網絡的的差異, 這一部分的功能在RMI開發中對程式員是透明的。Stub為用戶端編碼遠端指令并把他們發送到伺服器。而Skeleton則是把遠端指令解碼,調用服務端的遠端對象的方法,把結果在編 碼發給stub,然後stub再解碼傳回調用結果給用戶端。所有的操作如下圖所示

【初學】RMI(Remote Method Invocation)初窺門徑

Stub為遠端對象在用戶端的一個代理,當用戶端調用遠端對象的方法時,實際是委托Stub這個代理去調用遠端的對象,這個調用過程如下:

(1)初始化一個與遠端JVM的連接配接

(2)寫入并傳輸參數給遠端JVM

(3)執行遠端對象的方法調用,并等待調用結果的傳回(return)

(4)讀取調用的傳回值(也可能是一個異常)

(5)傳回調用的結果給調用者

這些操作(序列号參數, 建立Socket連接配接等等),都由這個Stub來透明化。

在遠端的JVM中,每一個對象(需要被遠端調用的對象)都有一個相應的skeleton(在Java2環境中,這個skeleton不是必須的,這個先不說),skeleton的作用是分發用戶端的調用到具體的實作類,skeleton接受 一個用戶端過來的調用過程如下:

(1)讀取用戶端傳遞過來的參數

(2)調用實作類的方法

(3)寫入并傳輸傳回結果給調用者,同樣的,這個結果也是函數調用結果或異常

2、Stub和Skeleton在什麼位置産生

Stub和Skeleton都是在伺服器産生的。

Stub存在于用戶端,作為用戶端的代理,讓我們總是認為用戶端産生了stub,接口沒有作用。實際上stub類是通過Java動态類下載下傳 機制下載下傳的,它是由服 務端産生,然後根據需要動态的加載到用戶端,如果下次再運作這個用戶端該存根類存在于classpath中,它就不需要再下載下傳了,而是直接加載。總的來說,stub是在服務端産生的,如果服務端的stub内容改變,那麼用戶端的也是需要同步更新。

【一個簡單的例子】

1、伺服器端

接口定義

package test.server;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IMyServer extends Remote {
	
	public int add(int a, int b) throws RemoteException;
	
	public int mul(int a, int b) throws RemoteException;
}
           

接口實作類

package test.server.impl;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import test.server.IMyServer;

public class MyServer extends UnicastRemoteObject implements IMyServer {
	private static final long serialVersionUID = 1L;

	public MyServer() throws RemoteException {
		super();
	}

	@Override
	public int add(int a, int b) throws RemoteException {
		return a + b;
	}

	@Override
	public int mul(int a, int b) throws RemoteException {
		return a * b;
	}
}
           

用于測試的伺服器端

package test.server.impl;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ServerTest {
	
	private static Registry registry = null;
	
	public static Registry getInstance() throws RemoteException {
		try {
			return registry == null ? (registry = LocateRegistry
					.createRegistry(1099)) : registry;
		} catch (RemoteException e) {
			throw e;
		}
	}
	
	private ServerTest() {
	}
	
	public static void main(String[] args) throws InterruptedException {
		try {
			MyServer server = new MyServer();
			getInstance().bind("server", server);
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (AlreadyBoundException e) {
			e.printStackTrace();
		}
		System.err.println("server bind end");
	}
}
           

2、客服端

首先客服端需要有遠端對象的接口IMyServer。

客服端測試類

package test.client;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import test.server.IMyServer;

public class ClientTest {
	public static void main(String[] args) {
		try {
			Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
			IMyServer server = (IMyServer) registry.lookup("server");
			int a = 2, b = 10;
			System.err.println(server.add(a, b));
			System.err.println(server.mul(a, b));
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (NotBoundException e) {
			e.printStackTrace();
		}
	}
}
           

3、運作

(1)先運作伺服器端的ServerTest

(2)運作用戶端的ClientTest,輸出12 20

【分析】

1、首先,遠端對象和調用傳遞的參數都必須實作接口Serializable接口,關系系列化與反系列化可以參看我的另一篇文章 《Java Serializable系列化與反系列化》

2、MyServer繼承UnicastRemoteObject的作用與好處

先來看看UnicastRemoteObject這個對象的結構,如下圖:

【初學】RMI(Remote Method Invocation)初窺門徑

(1)先來看看java.rmi.server.RemoteObject這個類,首先它實作了Serializable, Remote這兩個接口

RemoteObject的真正作用在于,他重寫了Object的hashCode、equals、和toString方法,重寫hashCode方法使得遠端對象可以存放在Hashtable等一些哈希結果的集合中,重寫equals方法使得遠端對象可以進行比較,而重寫後的toString方法傳回遠端對象的描述串。該類還實作專門的(私用)方法writeObject 和方法readObject,用于系列化與反系列化。

(2)java.rmi.server.RemoteServer類

在這個類中,則提供了一些對調試非常有用的方法,它一共有3個方法:

static String getClientHost():傳回一個客戶機主機的字元串表示形式,用于在目前線程中正在處理的遠端方法調用。

static PrintStream getLog():傳回用于 RMI 調用日志的流。

static void setLog(OutputStream out):将 RMI 調用記錄到輸出流 out 中。

同時要注意,該類是abstract的。

(3)java.rmi.server.UnicastRemoteObject類

再看看這個類,通常,遠端對象都 繼承UnicastRemoteObject類,UnicastRemoteObject 類提供遠端對象所需的基本行為。在這個類中提供了支援建立和導出遠端對象的一系列方法,一個對象繼承UnicastRemoteObject它将獲得以下特性:

A、對這種對象的引用至多僅在建立該遠端對象的程序生命期内有效

B、使得遠端對象既有使用TCP協定通信的能力(Socket)

C、對于用戶端與伺服器的調用、傳參、傳回值等操作使用流的方式來處理

其他的,java.rmi.registry.LocateRegistry類提供了一系列的方法用于建立、擷取Registry執行個體的方法;在Registry接口中,定義了一系列的方法,用于操作遠端對象包括:綁定對象(bind)、擷取對象(lookup)、重寫綁定(rebind)、解除綁定(unbind)和傳回系統資料庫綁定清單(list)方法(也可以使用java.rmi.Naming來操作)。

剛學習RMI,不對的地方歡迎指出

繼續閱讀