日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

系统间通信2:通信管理与远程方法调用RMI

發布時間:2025/3/15 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 系统间通信2:通信管理与远程方法调用RMI 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文引用 : https://yinwj.blog.csdn.net/article/details/49120813

RMI : Remote Method Invocation,遠程方法調用
RPC : Remote Procedure Call Protocol, 遠程過程調用協議
ESB : Enterprise Service Bus, 企業服務總線
SOA : Service-Oriented Architecture, 面向服務的架構

1. 概述

在這個章節我將通過對RMI的詳細介紹,引出一個重要的系統間通信的管理規范RPC,并且繼續討論一些RPC的實現;再通過分析PRC的技術特點,引出另一種系統間通信的管理規范ESB,并介紹ESB的一些具體實現。最后我們介紹SOA:面向服務的軟件架構。

2. RMI基本使用

RMI(Remote Method Invocation,遠程方法調用),是JAVA早在JDK 1.1中提供的JVM與JVM之間進行 對象方法調用的技術框架的實現(在JDK的后續版本中,又進行了改進)。通過RMI技術,某一個本地的JVM可以調用存在于另外一個JVM中的對象方法,就好像它僅僅是在調用本地JVM中某個對象方法一樣。例如RMI客戶端中的如下調用:

List< UserInfo > users = remoteServiceInterface.queryAllUserinfo();

看似remoteServiceInterface對象和普通的對象沒有區別,但實際上remoteServiceInterface對象的具體方法實現卻不在本地的JVM中,而是在某個遠程的JVM中(這個遠程的JVM可以是RMI客戶端同屬于一臺物理機,也可以屬于不同的物理機)

1.1 RMI使用場景

RMI是基于JAVA語言的,也就是說在RMI技術框架的描述中,只有Server端使用的是JAVA語言并且Client端也是用的JAVA語言,才能使用RMI技術(目前在codeproject.com中有一個開源項目名字叫做“RMI for C++”,可以實現JAVA To C++的RMI調用。但是這是一個第三方的實現,并不是java的標準RMI框架定義,所以并不在我們的討論范圍中)。

RMI適用于兩個系統都主要使用JAVA語言進行構造,不需要考慮跨語言支持的情況。并且對兩個JAVA系統的通訊速度有要求的情況。

RMI 是一個良好的、特殊的RPC實現:使用JRMP協議承載數據描述,可以使用BIO和NIO兩種IO通信模型。

1.2 RMI框架的基本組成

雖然RMI早在JDK.1.1版本中就開放了。但是在JDK1.5的版本中RMI又進行改進。所以我們后續的代碼示例和原理講解都基于最新的RMI框架特性。

要定義和使用一套基于RMI框架工作的系統,您至少需要做一下幾個工作:

  • 定義RMI Remote接口
  • 實現這個RMI Remote接口
  • 生成Stub(樁)和Skeleton(骨架)。這一步的具體操作視不同的JDK版本而有所不同(例如JDK1.5后,Skeleton不需要手動);“RMI注冊表”的工作方式也會影響“Stub是否需要命令行生成”這個問題。
  • 向“RMI注冊表”注冊在第2步我們實現的RMI Remote接口。
  • 創建一個Remote客戶端,通過java“命名服務”在“RMI注冊表”所在的IP:PORT尋找注冊好的RMI服務。
  • Remote客戶端向調用存在于本地JVM中對象那樣,調用存在于遠程JVM上的RMI接口。
  • 下圖描述了上述幾個概念名稱間的關系,呈現了JDK.5中RMI框架其中一種運行方式(注意,是其中一種工作方式。也就是說RMI框架不一定都是這種運行方式,后文中我們還將描述另外一種RMI的工作方式):

    1.3 RMI示例代碼

    在這個代碼中,我們將使用“本地RMI注冊表”(LocateRegistry),讓RMI服務的具體提供者和RMI注冊表工作在同一個JVM上,向您介紹最基本的RMI服務的定義、編寫、注冊和調用過程:

    首先我們必須定義RMI 服務接口,代碼如下:

    package testRMI;import java.rmi.Remote; import java.rmi.RemoteException; import java.util.List;import testRMI.entity.UserInfo;public interface RemoteServiceInterface extends Remote {/*** 這個RMI接口負責查詢目前已經注冊的所有用戶信息*/public List<UserInfo> queryAllUserinfo() throws RemoteException; }

    很簡單的代碼,應該不用多解釋什么了。這個定義的接口方法如果放在某個業務系統A中,您可以理解是查詢這個系統A中所有可用的用戶資料。注意這個接口所繼承的java.rmi.Remote接口,是“RMI服務接口”定義的特點。

    那么有接口定義了,自然就要實現這個接口:

    package testRMI;import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.ArrayList; import java.util.List;import testRMI.entity.UserInfo;/*** RMI 服務接口RemoteServiceInterface的具體實現<br>* 請注意這里繼承的是UnicastRemoteObject父類。* 繼承于這個父類,表示這個Remote Object是“存在于本地”的RMI服務實現* (這句話后文會解釋)* @author yinwenjie**/ public class RemoteUnicastServiceImpl extends UnicastRemoteObject implements RemoteServiceInterface {/*** 注意Remote Object沒有默認構造函數* @throws RemoteException*/protected RemoteUnicastServiceImpl() throws RemoteException {super();}private static final long serialVersionUID = 6797720945876437472L;/* (non-Javadoc)* @see testRMI.RemoteServiceInterface#queryAllUserinfo()*/@Overridepublic List<UserInfo> queryAllUserinfo() throws RemoteException {List<UserInfo> users = new ArrayList<UserInfo>();UserInfo user1 = new UserInfo();user1.setUserAge(21);user1.setUserDesc("userDesc1");user1.setUserName("userName1");user1.setUserSex(true);users.add(user1);UserInfo user2 = new UserInfo();user2.setUserAge(21);user2.setUserDesc("userDesc2");user2.setUserName("userName2");user2.setUserSex(false);users.add(user2);return users;} }

    還有我們定義的Userinfo信息,就是一個普通的POJO對象:

    package testRMI.entity;import java.io.Serializable; import java.rmi.RemoteException;public class UserInfo implements Serializable {/*** */private static final long serialVersionUID = -377525163661420263L;private String userName;private String userDesc;private Integer userAge;private Boolean userSex;public UserInfo() throws RemoteException {}/*** @return the userName*/public String getUserName() {return userName;}/*** @param userName the userName to set*/public void setUserName(String userName) {this.userName = userName;}/*** @return the userDesc*/public String getUserDesc() {return userDesc;}/*** @param userDesc the userDesc to set*/public void setUserDesc(String userDesc) {this.userDesc = userDesc;}/*** @return the userAge*/public Integer getUserAge() {return userAge;}/*** @param userAge the userAge to set*/public void setUserAge(Integer userAge) {this.userAge = userAge;}/*** @return the userSex*/public Boolean getUserSex() {return userSex;}/*** @param userSex the userSex to set*/public void setUserSex(Boolean userSex) {this.userSex = userSex;} }

    RMI Server 的接口定義和RMI Server的實現都有了,那么編寫代碼的最后一步是**將這個RMI Server注冊到“RMI 注冊表”中運行。這樣 RMI的客戶端就可以調用這個 RMI Server了。**下面的代碼是將RMI Server注冊到“本地RMI 注冊表”中:

    package testRMI;import java.rmi.Naming; import java.rmi.registry.LocateRegistry;public class RemoteUnicastMain {public static void main(String[] args) throws Exception {/** Locate registry,您可以理解成RMI服務注冊表,或者是RMI服務位置倉庫。* 主要的作用是維護一個“可以正常提供RMI具體服務的所在位置”。* 每一個具體的RMI服務提供者,都會講自己的Stub注冊到Locate registry中,以表示自己“可以提供服務”* * 有兩種方式可以管理Locate registry,一種是通過操作系統的命令行啟動注冊表;* 另一種是在代碼中使用LocateRegistry類。* * LocateRegistry類中有一個createRegistry方法,可以在這臺物理機上創建一個“本地RMI注冊表”* */LocateRegistry.createRegistry(1099);// 以下是向LocateRegistry注冊(綁定/重綁定)RMI Server實現。RemoteUnicastServiceImpl remoteService = new RemoteUnicastServiceImpl();// 通過java 名字服務技術,可以講具體的RMI Server實現綁定一個訪問路徑。注冊到LocateRegistry中Naming.rebind("rmi://127.0.0.1:1099/queryAllUserinfo", remoteService);/** 在“已經擁有某個可訪問的遠程RMI注冊表”的情況下。* 下面這句代碼就是向遠程注冊表注冊RMI Server,* 當然遠程RMI注冊表的JVM-classpath中一定要有這個Server的Stub存在* * (運行在另外一個JVM上的RMI注冊表,可能是同一臺物理機也可能不是同一臺物理機)* Naming.rebind("rmi://192.168.61.1:1099/queryAllUserinfo", remoteService);* */} }

    這樣我們后續編寫的Client端就可以調用這個RMI Server了。下面的代碼是RMI Client的代碼:

    package testRMI;import java.rmi.Naming; import java.util.List;import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.BasicConfigurator;import testRMI.entity.UserInfo;/*** 客戶端調用RMI測試* @author yinwenjie**/ public class RemoteClient {static {BasicConfigurator.configure();}/*** 日志*/private static final Log LOGGER = LogFactory.getLog(RemoteClient.class);public static void main(String[] args) throws Exception {// 您看,這里使用的是java名稱服務技術進行的RMI接口查找。RemoteServiceInterface remoteServiceInterface = (RemoteServiceInterface)Naming.lookup("rmi://192.168.61.1/queryAllUserinfo");List<UserInfo> users = remoteServiceInterface.queryAllUserinfo();RemoteClient.LOGGER.info("users.size() = " +users.size());} }

    那么怎么來運行這段代碼呢?如果您使用的是eclipse編寫了您第一個RMI Server和RMI Client,并且您使用的是“本地RMI 注冊表”。那么您不需要做任何的配置、腳本指定等工作(包括不需要專門設置JRE權限、不需要專門指定classpath、不需要專門生成Stub和Skeleton),就可以看到RMI的運行和調用效果了:

    下圖為RemoteUnicastMain的效果RMI 服務注冊和執行效果:

    可以看到,RemoteUnicastMain中的代碼執行完成后整個應用程序沒有退出。如下圖:

    這是因為這個應用程序要承擔“真實的RMI Server實現”的服務調用。如果它退出,RMI 注冊表就無法請求真實的服務實現了
    我們再來看下圖,RemoteClient調用RMI 服務的效果:

    很明顯控制臺將返回:

    0 [main] INFO testRMI.RemoteClient - users.size() = 2

    3. JAVA RMI 原理

    通過上面的兩組代碼,我們大概知道了RMI框架是如何使用的。下面我們來講解一下RMI的基本原理。

    3.1 Registry和Stub、Skeleton的關系

    • 一定要說明,在RMI Client實施正式的RMI調用前,它必須通過LocateRegistry或者Naming方式到RMI注冊表尋找要調用的RMI注冊信息。找到RMI事務注冊信息后,Client會從RMI注冊表獲取這個RMI Remote Service的Stub信息。這個過程成功后,RMI Client才能開始正式的調用過程。

    • 另外要說明的是RMI Client正式調用過程,也不是由RMI Client直接訪問Remote Service,而是由客戶端獲取的Stub作為RMI Client的代理訪問Remote Service的代理Skeleton,如上圖所示的順序。也就是說真實的請求調用是在Stub-Skeleton之間進行的。

    • Registry并不參與具體的Stub-Skeleton的調用過程,只負責記錄“哪個服務名”使用哪一個Stub,并在Remote Client詢問它時將這個Stub拿給Client

    3.2 Remote-Service線程管理

    在上文中的演示我們看到了RemoteRegistryUnicastMain處理請求時,使用了線程池。這是JDK1.5到JDK1.6+版本中RMI框架的做的一個改進。包括JDK1.5在內,之前的版本都采用新建線程的方式來處理請求;在JDK1.6版本之后,改用了線程池,并且線程池的大小是可以調整的:

    • sun.rmi.transport.tcp.maxConnectionThreads:連接池的大小,默認為無限制。無限的大小肯定是有問題,按照Linux單進程可打開的最大文件數限制,建議的設置值為65535(生產環境)。如果同一時間連接池中的線程數量達到了最大值,那么后續的Client請求將會報錯。測試環境/開發環境是否設置這個值,就沒有那么重要了。

    • sun.rmi.transport.tcp.threadKeepAliveTime:如果當線程池中有閑置的線程資源的話,那么這個閑置線程資源多久被注銷(單位毫秒),默認的設置是1分鐘。

    如果您使用的是linux或者window的命令控制臺執行的話,您可以通過類似如下語句進行參數設置:

    java -Dsun.rmi.transport.tcp.maxConnectionThreads=2 -Dsun.rmi.transport.tcp.threadKeepAliveTime=1000 testRMI.RemoteRegistryUnicastMain

    3.3 Registry和Naming

    Registry和Naming都可以進行RMI服務的bind/rebind/unbind,都可以用lookup方法查詢RMI服務。Naming實際上是對Registry的封裝。使用完整的URL方式對已注冊的服務名進行查找。

    3.4 UnicastRemoteObject和Activatable

    在JDK1.2版本中,由Ann Wollrath執筆加入了一種新的RMI工作方式。即通過RMI“活化”模式,將Remote Service的真實提供者移植到RMI Registry注冊表所在的JVM上。要使用這種工作模式的Remote Service實現不再繼承UnicastRemoteObject類,而需要繼承Activatable類(其他的業務代碼不需要改變)

    4. RMI:一種特殊的RPC服務實現

    之所以介紹RMI,是因為要通過介紹RMI引出一種重要的系統間通訊管理框架RPC.

    總結

    以上是生活随笔為你收集整理的系统间通信2:通信管理与远程方法调用RMI的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。