一 RMI系统运行机理
RMI应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务器应用程序将创建多个
远程对象,使这些远程对象能够被引用,然后等待客户机调用这些远程对象的方法。而典型的客户机程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI为服务器和客户机进行通信和信息传递提供了一种机制。
在与远程对象的通信过程中,RMI使用标准机制:stub和skeleton。远程对象的stub担当远程对象的客户本地代表或代理人角色。调用程序将调用本地stub的方法,而本地stub将负责执行对远程对象的方法调用。在RMI中,远程对象的stub与该远程对象所实现的远程
接口集相同。调用 stub的方法时将执行下列操作:
(1) 初始化与包含远程对象的远程虚拟机的连接;
(2) 对远程虚拟机的参数进行编组(写入并传输);
(3) 等待方法调用结果;
(4) 解编(读取)返回值或返回的
异常;
(5) 将值返回给调用程序。为了向调用程序展示比较简单的调用机制,stub将参数的
序列化和网络级通信等细节隐藏了起来。在远程虚拟机中,每个远程对象都可以有相应的skeleton(在JDK1.2环境中无需使用skeleton)。Skeleton负责将调用分配给实际的远程对象实现。它在接收方法调用时执行下列操作:(1) 解编(读取)远程方法的参数;(2) 调用实际远程对象实现上的方法;(3) 将结果(返回值或异常)编组(写入并传输)给调用程序。stub和skeleton由rmic编译器生成。
利用RMI编写分布式对象应用程序需要完成以下工作:(1) 定位远程对象。应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用RMI的简单命名工具rmiregistry来注册它的远程对象,也可以将远程对象引用作为常规操作的一部分来进行传递和返回。(2)与远程对象通信。远程对象间通信的细节由RMI处理,对于
程序员来说,远程通信看起来就像标准的Java方法调用。(3)给作为参数或返回值传递的对象加载类字节码。因为RMI允许调用程序将纯Java对象传给远程对象,所以,RMI将提供必要的机制,既可以加载对象的代码又可以传输对象的数据。在RMI分布式应用程序运行时,服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器上的注册服务程序中用远程对象的名字查找该远程对象,然后调用它的方法。
二 远程接口概念:
RMI对接口有着强烈的依赖。在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节。所以客户得到远程对象的一个句柄正好同一些本地的根代码连接,有后者负责通过网络通信。但我们并不关心这些事情,通过自己的接口句柄发送消息即可。
创建一个远程接口时,必须遵守下列规则:
1) 远程接口必须为public属性(不能有“包访问”;也就是说,他不能是“友好的”)。否则,一旦客户试图装载一个实现了远程接口的远程对象,就会得到一个
错误。
2) 远程接口必须扩展接口java.rmi.Remote。
3) 除与应用程序本身有关的违例,远程接口中的每个方法都必须在自己的throws从句中声明java.rmi.RemoteException.
4) 作为参数或返回值传递的一个远程对象(不管是直接,还是本地对象中嵌入)必须声明为远程接口,不可声明为实施类。
三 远程接口的实施:
服务器必须包含一个扩展了UnicastRemoteObject类,并实现远程接口。这个类也可以含有附加的方法,但客户只能使用远程接口中的方法。因为客户是指向接口的一个句柄,而不是它的哪个类。
必须为远程对象定义构造方法,即使只准备定义一个默认构造方法,用它调用基础类构造方法。必须把它明确地编写出来,因为它必须“掷”出RemoteException违例。
四 代码存根:
当客户代码调用一个远程对象上的远程方法是,实际上是调用一个Java
编程语言的普通方法,这个方法是封装在stub(代码存根)的代用对象。存根(Stub)是远程对象在客户端的代理,它将RMI调用传递给服务器端的骨架(Skeleton),后者负责将该调用传递给实际的远程方法。
要完成这个工作可使用rmic编译器,rmic编译器生成远程对象的存根和骨架。
代码存根在服务器端创建,必须驻留于客户端。
五 RMI实战
一个正常工作的RMI系统由下面几个部分组成:
● 远程服务接口的定义
● 远程服务接口的具体实现
● 桩(Stub)和框架(Skeleton)文件
● 一个运行远程服务的服务器
● 一个RMI命名服务,它允许客户端去
发现这个远程服务
● 类文件的提供者(一个
HTTP或者FTP服务器)
● 一个需要这个远程服务的客户端程序
如果所有的RMI文件都已经设计好了,那么需要下面的几个步骤去生成系统:
1、 编写并且编译接口的Java代码
2、 编写并且编译接口实现的Java代码
3、 从接口实现类中生成桩(Stub)和框架(Skeleton)类文件
4、 编写远程服务的主运行程序
5、 编写RMI的客户端程序
6、 安装并且运行RMI系统
实现过程如下:(以下代码在Windows Server 2003,JDK1.6环境下调试通过,代码来自互联网)
服务器端:
1接口
第一步就是建立和编译服务接口的Java代码。这个接口定义了所有的提供远程服务的功能,下面是源程序:
Product,java
import java.rmi.*;
public interface Product extends Remote
{
String getDescription() throws RemoteException;
}
2接口的具体实现
下一步,我们就要写远程服务的具体实现,这是一个ProductImpl类文件:
ProductImpl.java
import java.rmi.*;
import java.rmi.server.*;
public class ProductImpl extends UnicastRemoteObject implements Product
{
private String name ;
public ProductImpl(String n) throws RemoteException
{
name = n;
}
public String getDescription()
{
return "Hello,I am " + name + " . I love you !";
}
}
3 桩(Stubs)和框架(Skeletons)
下一步就是要使用RMI编译器rmic来生成桩和框架文件,这个编译运行在远程服务实现类文件上。
>rmic ProductImpl
在你的目录下运行上面的命令,成功执行完上面的命令你可以发现一个ProductImpl_stub.class文件,如果你是使用的是1.2以前的SDK,那么你还可以发现ProductImpl_Skel.class文件。
4 主机服务器
远程RMI服务必须是在一个服务器中运行的。
ProductServer.java
import java.rmi.*;
import java.rmi.server.*;
public class ProductServer {
public static void main(String args[])
{
try
{
System.out.println("Construction server implementats ...");
ProductImpl p1 = new ProductImpl("Wang.yuanbin");
ProductImpl p2 = new ProductImpl("Bueaty");
System.out.println("binding server implementation to registry ...");
Naming.rebind("wyb",p1);
Naming.rebind("Beau",p2);
System.out.println("Waiting for invocations from clients ...");
}
catch (Exception ex)
{
System.out.println("Error: " + ex );
}
}
}
5 客户端
ProductClient.java
import java.rmi.*;
import java.rmi.server.*;
public class ProductClient
{
public static void main(String [] args)
{
System.setSecurityManager(new RMISecurityManager());
String url = "rmi://localhost/";
try
{
Product c1 = (Product) Naming.lookup(url + "wyb");
Product c2 = (Product) Naming.lookup(url + "Beau");
System.out.println(c1.getDescription());
System.out.println(c2.getDescription());
}
catch (Exception ex)
{
System.out.println("Error : " + ex);
}
System.exit(0);
}
}
在该类中使用java.rmi.Naming中的lookup()方法获得对远程对象的引用,依据需要调用该引用的远程方法,其调用方式和对本地对象方法的调用相同。.
6 安全策略文件
因为RMI的安全机制将在
服务端发生作用,所以你必须增加一条安全策略。以下是对应安全策略的
例子。
ProductServer.policy和ProductClient.policy
grant {
permission java.security.AllPermission "", "";
};
注意:这是一条最简单的安全策略,它允许任何人做任何事,对于你的更加关键性的应用,你必须指定更加详细安全策略。
如果没有上面的安全策略,运行客户端时,会出现如下错误:
E:\wx\2>java ProductClient
Error : java.security.AccessControlException: access denied (java.net.SocketPerm
ission 127.0.0.1:1099 connect,resolve)
7 运行RMI系统
按如下步骤编译、运行系统:
1)为接口、实现和客户、服务器类编译源文件:
Javac Product*.java
2)在实现类上运行rmic
Rmic ProductImpl
3)启动rmi注册程序
Start rmiregistry
4) 启动服务器
start java -Djava.security.policy=ProductServer.policy ProductServer
如果不指定安全策略,服务器将无法提供服务,一运行即关闭;指定安全策略后,运行后显示:
Construction server implementats ...
binding server implementation to registry ...
Waiting for invocations from clients ...
5) 运行客户程序
Java –Djava.security.policy=ProductClient.policy ProductClient
运行后屏幕显示如下:
E:\wx\2>java -Djava.security.policy=ProductClient.policy ProductClient
Hello,I am Wang.yuanbin . I love you !
Hello,I am Bueaty . I love you !
E:\wx\2>
8 错误分析:(以下代码同样来自互联网,但未调试通过)
在我测试RMI的过程中,曾经出现如下错误,但未找出原因:
E:\wx>java -Djava.security.policy=RmiHelloClient.policy RmiHelloClient
java.rmi.ConnectException: Connection refused to host: localhost; nested excepti
on is:
java.net.ConnectException: Connection refused: connect
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(Unknown Source)
at sun.rmi.transport.tcp.TCPChannel.createConnection(Unknown Source)
at sun.rmi.transport.tcp.TCPChannel.newConnection(Unknown Source)
at sun.rmi.server.UnicastRef.newCall(Unknown Source)
at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
at java.rmi.Naming.lookup(Unknown Source)
at RmiHelloClient.main(RmiHelloClient.java:19)
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.PlainSocketImpl.
socketConnect(Native Method)
at java.net.PlainSocketImpl.doConnect(Unknown Source)
at java.net.PlainSocketImpl.connectToAddress(Unknown Source)
at java.net.PlainSocketImpl.connect(Unknown Source)
at java.net.SocksSocketImpl.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at java.net.Socket.<init>(Unknown Source)
at java.net.Socket.<init>(Unknown Source)
at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(Unknown S
ource)
at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(Unknown S
ource)
... 7 more
出错的代码如下:
远程接口
import java.rmi.*;
public interface RmiHelloRemoteIntfc extends Remote
{
String helloRemoteObj(String client) throws RemoteException;
}
远程接口实现
import java.rmi.server.*;
import java.rmi.*;
public class RmiHelloRemoteObj extends UnicastRemoteObject implements RmiHelloRemoteIntfc
{
public RmiHelloRemoteObj() throws RemoteException
{
super();
}
public String helloRemoteObj(String client) throws RemoteException
{
return "Hello World"+client;
}
}
服务器
import java.io.*;
import java.rmi.*;
import java.rmi.server.*;
import sun.applet.*;
import java.rmi.registry.LocateRegistry;
public class RmiHelloServer
{
public RmiHelloServer()
{
}
public static void main(String[] args)
{
//创建并安装安全管理器
if(System.getSecurityManager()==null)
{
System.setSecurityManager(new RMISecurityManager());
}
try{
//创建远程对象
RmiHelloRemoteObj ttt=new RmiHelloRemoteObj();
//启动
注册表
LocateRegistry.createRegistry(4588);
//奖名称绑定到对象
//System.setProperty("java.rmi.server.localhost","211.81.207.109");
Naming.rebind("//localhost/wx",ttt);
System.out.println("RMI服务器正在运行。。。。。。");
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
客户端
import java.rmi.*;
import java.rmi.server.*;
public class RmiHelloClient
{
public RmiHelloClient()
{
}
public static void main(String[] args)
{
//创建并安装安全管理器
if(System.getSecurityManager()==null)
{
System.setSecurityManager(new RMISecurityManager());
}
try{
RmiHelloRemoteIntfc c1=(RmiHelloRemoteIntfc)Naming.lookup("rmi://localhost/wx");
System.out.println(c1.helloRemoteObj("Everyone"));
}
catch(Exception e)
{
e.printStackTrace();
}
System.exit(0);
}
}
策略文件
grant codeBase
"file:/e:/wx/"
{
// permission java.net.SocketPermission
// "*:1000-65535","accept,connect,listen,resolve";
permission java.security.AllPermission "", "";
};