一.客户与服务器角色
在传统的客户/服务器模式中,客户请求服务器。服务器解析传输的请求,并得到响应,再传给客户端。但是这样必须考虑中间传输格式的解析。如果客户端无须关心请求的传输和解析,只调用本地方法的形式得到结果,但是有时提供服务的对象不再同一个虚拟机内,甚至不是java虚拟机,那怎么办,解决的办法是在客户端提供一个服务器的代理,客户直接调用这个代理,这个代理负责客户与服务器交流。同样,在服务器也安装一个代理负责服务器与客户端通信。并以常规方式调用服务器方法。接下来的问题就是代理之间是怎么通信的呢。
RMI----------------Java远程方法调用技术,支持java的分布式对象之间的调用
CORBA------------通用对象请求架构,支持任何编程语言编写的对象之间的方法调用,CORBA使用Internet inter-ORB协议(IIOP)支持对象间的通信
SOAP--------------简单对象访问协议,它也独立编程语言。基于XML的传输格式。
另外,MicroSoft也使用另一种底层协议COM支持对象间通信。
CORBA和SOAP都是独立于语言的,客户端和服务器完全使用C,C++,Java来编写,只要你提供一种接口描述,以说明你的对象能够处理的方法的签名和数据类型。该接口描述是一种特殊的描述语言,对于CORBA来说接口定义语言是(IDL),对于SOAP则是Web服务描述语言(WSDL)。
CORBA更加高效,而SOAP更适合Web架构的系统。如果通信的对象都是Java实现的,那么最好使用RMI。
二.远程方法调用
客户端对象------------------一般是发起远程调用的对象
服务器对象------------------相应的远程对象
2.1 存根和参数编组
存根-------------当客户端代码调用一个远程方法时,实际上是调用的一个本地方法,我们称此代理对象为存根,存根位于客户端上,而不是服务器上,存根将调用远程方法所需的参数打包成一组字节,这个打包过程是与硬件无关的编码的编码方式进行编码。
参数编组--------对参数编码的过程叫参数编组,它的目的是将参数转换成适合在虚拟机传输的格式。
总之,客户端的存根构造一个信息块,它由以下几部分组成:
- 被使用的远程对象的标示符
- 被调用的方法的描述
- 编组后的参数
然后将这个信息块发送到服务器,服务器接受对象为每个远程方法执行以下动作
- 反编组参数
- 定位要调用的对象
- 调用所需的方法
- 捕获返回值或者调用产生的异常,对它进行编组;
- 对返回值编组,打包送回客户端存根
客户端存根对接受到得返回值编组进行反编组,如果远程方法抛出异常,客户端存根在客户端处理空间中重新抛出异常。
2.2 动态类加载
当一个远程对象,作为远程方法的参数或者返回值,传递给另一个程序时,该程序很显然需要这个对象的Class文件。
三.配置远程方法调用
首先,你必须在客户端和服务器同时运行程序,其次,必要的对象信息被划分为客户端接口和服务器实现。还有专门的查询机制,能够使得客户端准确定位服务器上的对象
3.1 接口与实现
客户端可能需要操纵服务器对象,但是实际上它并不需要真正的服务器对象的引用,因为它只要知道这个对象时干什么的就可以了,我们可以使用接口的方式来描述它能做什么。而这些接口是客户端和服务器共享的,它必须同时存在于客户端和服务器。注意的是远程对象的接口必须继承与Remote。而且必须抛出RemoteException异常。
package rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Product extends Remote {
String getDescription() throws RemoteException;
}
然后呢,在服务器必须实现这个接口
服务器类通常继承RemoteServer。
package rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class ProductImpl extends UnicastRemoteObject implements Product {
private String desc;
public ProductImpl(String desc) throws RemoteException {
super();
this.desc = desc;
}
@Override
public String getDescription() throws RemoteException {
// TODO Auto-generated method stub
return "I am a "+desc+"buy me!";
}
}
3.2 存根类生成
从JDK5.0开始,存根类可以自动生成,但是在这之前,存根类必须手动生成:
rmic -v1.2 ProductImpl
它会生成两个文件ProductImpl_Stub.class和ProductImpl_Skel.class
3.3 定位服务器对象
要访问服务器的远程对象,必须获得一个客户端存根,最直接的方法时调用远程方法,获得返回值返回存根,但是这样第一个远程对象怎么获得呢。RMI类库提供了自举注册服务来定位第一个服务器对象。通过给自举注册服务提供一份对象的引用和名字,就可以注册服务器对象,客户端就可以获得对象的存根。这些对象的名称最好唯一。
package rmi;
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class ProductServer {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
System.out.println("Constructing server implementations...");
ProductImpl p1=new ProductImpl("boy");
ProductImpl p2=new ProductImpl("girl");
System.out.println("Binding server implementations to registry...");
Context namingContext = new InitialContext();
namingContext.bind("rmi:b",p1);
namingContext.bind("rmi:g",p2);
System.out.println("Waiting for invocation from clients....");
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
1.启动服务器(windows)
start rmiregistry
start java ProductServer
2.列举远程对象
NamingEnumeration<NameClassPair> e = namingContext.list("rmi:");
package rmi2.ShowBindings;
/**
@version 1.10 2004-08-14
@author Cay Horstmann
*/
import java.rmi.*;
import java.rmi.server.*;
import javax.naming.*;
/**
This programs shows all RMI bindings.
*/
public class ShowBindings
{
public static void main(String[] args)
{
try
{
Context namingContext = new InitialContext();
NamingEnumeration<NameClassPair> e = namingContext.list("rmi:");
while (e.hasMore())
System.out.println(e.next().getName());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
3.4 编写客户端代码
使用RMI的客户端必须安装一个安全管理器,用以动态加载存根的行为。
System.setSecurityManager(new RMIsetSecurityManager)
package rmi2.Product;
/**
@version 1.20 2004-08-15
@author Cay Horstmann
*/
import java.rmi.*;
import java.rmi.server.*;
import javax.naming.*;
/**
This program demonstrates how to call a remote method
on two objects that are located through the naming service.
*/
public class ProductClient
{
public static void main(String[] args)
{
System.setProperty("java.security.policy", "client.policy");
System.setSecurityManager(new RMISecurityManager());
String url = "rmi://localhost/";
// change to "rmi://yourserver.com/" when server runs on remote machine yourserver.com
try
{
Context namingContext = new InitialContext();
Product c1 = (Product) namingContext.lookup(url + "toaster");
Product c2 = (Product) namingContext.lookup(url + "microwave");
System.out.println(c1.getDescription());
System.out.println(c2.getDescription());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
要允许客户端连接RMI注册表以及服务器对象,必须提供一个策略文件。服务器对象使用的是大于1024的端口,RMI默认端口是1099.
client.policy
grant
{
permission java.net.SocketPermission
"*:1024-65535", "connect,accept";
permission java.net.SocketPermission
"localhost:80", "connect";
};
总之。
- 编译接口,实现类,服务器类,客户端类等文件
- 启动RMI注册表 start rmiregistry
对于RMI的部署相当的麻烦,一般初学者很容易出错,我也没有深入研究学习,在这里我也不说了。有兴趣的人可以上Google搜索。
四.远程方法的参数传递
4.1 传递非远程对象
当一个不是远程对象的对象,需要从一个java虚拟机传送另外一个java虚拟机时,第一个java虚拟机会制作一个对象COPY,然后通过网络传送到另外一个虚拟机,当你将对象传递到本地方法的是,传递的只是引用。
所以远程对象通过存根传递的,非远程对象通过复制传递。
无论何时,当调用远程方法的时候,存根将所有参数值的副本打包,发给服务器,其中用到了对象序列化机制编组参数。