参考:http://blog.csdn.net/smcwwh/article/details/7080997
1.客户与服务器的角色
所有分布式编程技术的基本思想都很简单:客户计算机产生一个请求,然后将这个请求通过网络发送到服务器。服务器处理这个请求,并发送回一个针对客户端的响应,供客户端进行分析。
这些请求和响应并不是在 Web 应用程序中看到的请求和响应。这里的客户端并非是 Web 浏览器,它可以是执行具有任意复杂度的业务规则的任何应用程序。客户端应用程序可以与人类之间进行交互,但也可以没有这种交互。
我们真正想要的是这样一种机制,客户端程序员以常规的方式进行方法调用,而无需操心将数据发送到网络上或者解析响应之类的问题。解决的办法是,在客户端为远程对象安装一个代理。代理位于客户端虚拟机中的换一个对象,它对于客户端程序来说,看起来就像是要访问的远程对象一样。客户调用此代理时,只需进行常规的方法调用。而客户端代理则负责使用网络协议与服务器进行联系。同样,实现服务的程序员也不希望因与客户端之间的通信被绊住。解决方法是在服务器端安装第二个代理对象。该服务器代理与客户端代理进行通信,并且它将以常规方式调用服务器对象上的方法。
代理之间是如何通信的呢?这要看以什么技术来实现它们。通常有三种选择:
CORBA:通用对象请求代理架构,支持任何编程语言编写的对象之间的方法调用。CORBA 使用二进制的 Internet Inter-ORB 协议(IIOP)来实现对象间的通信。
Web 服务架构的一个协议集,有时统一描述为 WS-*:它也是独立于编程语言的,不过它使用基于 XML 的通信格式。用于传输对象的格式则是简单的对象访问协议(SOAP)。
RMI:Java 的远程方法调用技术,支持 Java 分布式对象之间的方法调用。
CORBA 与 SOAP 都是完全独立于语言的。你只需提供一个接口描述,以说明你的对象的方法的签名以及能够处理的数据类型。该接口描述的是用一种特殊的语言编写的,对于 CORBA 来说是接口定义语言(IDL),对于 Web 服务而言则是 Web 服务描述语言(WSDL)。
2.远程方法调用
分布式计算的关键是远程方法调用。在一台机器(称为客户端)上的某些代码希望调用在另一台机器(远程对象)上的某个对象的一个方法。要实现这一点,方法的参数必须以某种方式传递到另一台机器上,而服务器必须得到通知,去定位远程对象并执行要调用的方法,并且必须将返回值传递回去。
客户端/服务器 这一术语用法只适用于单个的方法调用。调用远程方法的计算机对于这个调用来说是客户端,而处理这个调用的对象的宿主计算机对于这个调用来说是服务器。在某些情况下这两个角色完全有可能会颠倒过来,前一个调用的服务器在调用驻留在另一台计算机上的对象的某个远程方法时,它自己就变成了客户端。
当客户代码要在远程对象上调用一个远程方法时,实际上调用的是代理对象上的一个普通的方法,我们称此代理对象为存根(stub)。
存根位于客户端机器上,而非服务器上,它知道如何通过网络与服务器联系。存根会将远程方法所需的参数打包成一组字节。对参数编码的过程称作参数编组(parameter marshalling),参数编组的目的是将参数转换成适合在虚拟机之间进行传递的格式。在 RMI 协议中,对象是使用序列化机制进行编码的。在 SOAP 协议中,对象被编码为 XML。
总的来说,客户端的存根方法构造了一个信息块,它由以下几部分组成:
被使用的远程对象的标识符。
被调用的方法的描述。
编组后的参数。
然后,存根将此信息发送给服务器。在服务器一端,接收器对象执行以下动作:
定位要调用的远程对象。
调用所需的方法,并传递客户端提供的参数。
捕获返回值或该调用产生的异常。
将返回值编组,打包送回给客户端存根。
客户端存根对来自服务器端的返回值或异常进行反编组,其结果就成为调用存根的返回值。如果远程方法抛出了一个异常,那么存根就在客户端的虚拟机中重新抛出该异常。
这个过程显然很复杂,不过好消息是,这一切都是完全自动的,而且在很大程度上它对程序员是透明的。
实现远程对象和获取客户端存根的细节有赖于分布式对象采用的技术。
3.RMI 编程模型
远程对象的能力是由在客户端和服务端之间共享的接口所表示的。
远程对象的接口必须扩展 Remote 接口,它位于 java.rmi 包中。接口中的所有方法还必须声明抛出 RemoteException 异常,这是因为远程方法调用与生俱来就缺乏本地调用的可靠性,远程调用总是存在失败的可能。客户端代码必须时刻装备好处理这些问题。基于这些原因,Java 编程语言要求每一次远程方法调用都必须捕获 RemoteException,并且指明当调用不成功时应执行的相应的处理操作。
接下来,在服务器端必须提供这样的类,它真正实现了在远程接口中声明的工作。
通常,远程对象都 继承UnicastRemoteObject类,UnicastRemoteObject 类提供远程对象所需的基本行为。在这个类中提供了支持创建和导出远程对象的一系列方法,一个对象继承UnicastRemoteObject它将获得以下特性:
A、对这种对象的引用至多仅在创建该远程对象的进程生命期内有效
B、使得远程对象既有使用TCP协议通信的能力(Socket)
C、对于客户端与服务器的调用、传参、返回值等操作使用流的方式来处理
Unicast 这个术语是指我们通过产生对单一的 IP 地址和端口的调用来定位远程对象这一事实。这是 Java SE 中唯一支持的机制。更复杂的分布式对象系统(诸如 JINI)会考虑到对在多个不同的服务器上的远程对象的 “Multicast” 查找。
要访问服务器上的一个远程对象时,客户端首先需要一个本地的存根对象。可是客户端如何请求得到该存根呢?最普通的方法是调用另一个服务对象上的一个远程方法,以返回值的方式取得存根对象。
第一个远程对象总要通过某种方式进行定位。为此,JDK 提供了自举注册服务。服务器程序应该使用自举注册服务来注册至少一个远程对象。要注册一个远程对象,需要一个 RMI URL 和一个对实现对象的引用。RMI URL 以 rmi: 开头,后接服务器以及一个可选的接口号,接着是远程对象的(希望是)唯一的名字。
例如:rmi://regserver.mycompany.com:99/central_warehouse
默认情况下,主机名是 localhost,端口号为 1099。服务器告诉注册表在给定位置将这个名字关联或“绑定”到该对象。
基于安全原因,一个应用只有当它与注册表运行在同一个服务器时,该应用才可以绑定、取消绑定,或重新绑定注册对象的引用。这可以防止有恶意的客户端修改注册表信息。但是,任何客户端都可以查找对象。
在一个全局的注册表中,想保持一个名字的唯一性非常困难,因此不应该将此技术作为定位服务器端对象的通用方法。相反,自举服务只应该用来注册非常少的远程对象。然后使用这些对象来定位其他的对象。
1 package rmi; 2 3 import java.rmi.Remote; 4 import java.rmi.RemoteException; 5 6 public interface Warehouse extends Remote { 7 8 double getPrice(String description) throws RemoteException; 9 10 }
1 package rmi; 2 3 import java.rmi.RemoteException; 4 import java.rmi.server.UnicastRemoteObject; 5 import java.util.HashMap; 6 import java.util.Map; 7 8 public class WarehouseImpl extends UnicastRemoteObject implements Warehouse { 9 10 private Map<String, Double> prices; 11 12 public WarehouseImpl() throws RemoteException { 13 prices = new HashMap<>(); 14 prices.put("Blackwell Toaster", 24.95); 15 prices.put("ZapXpress Microwave Oven", 49.95); 16 } 17 18 @Override 19 public double getPrice(String description) throws RemoteException { 20 // TODO Auto-generated method stub 21 Double price = prices.get(description); 22 return prices == null ? 0 : price; 23 } 24 25 }
1 package rmi; 2 3 import java.rmi.RemoteException; 4 import java.rmi.registry.LocateRegistry; 5 6 import javax.naming.Context; 7 import javax.naming.InitialContext; 8 import javax.naming.NamingException; 9 10 public class Server { 11 12 public static void main(String[] args) { 13 try { 14 System.out.println("Constructing server implementation .."); 15 WarehouseImpl centralWarehouse = new WarehouseImpl(); 16 17 System.out.println("Binding server implementation to registry .."); 18 Context namingContext = new InitialContext(); 19 LocateRegistry.createRegistry(1099); 20 namingContext.bind("rmi://localhost:1099/central_warehouse", centralWarehouse); 21 22 System.out.println("Waiting for invocations from clients .."); 23 } catch (RemoteException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } catch (NamingException e) { 27 // TODO Auto-generated catch block 28 e.printStackTrace(); 29 } 30 31 } 32 33 }
1 package rmi; 2 3 import java.rmi.RemoteException; 4 5 import javax.naming.Context; 6 import javax.naming.InitialContext; 7 import javax.naming.NamingException; 8 9 public class Client { 10 11 public static void main(String[] args) { 12 try { 13 Context context = new InitialContext(); 14 Warehouse warehouse = (Warehouse) context.lookup("rmi://localhost:1099/central_warehouse"); 15 double price = warehouse.getPrice("Blackwell Toaster"); 16 System.out.println(price); 17 } catch (NamingException e) { 18 // TODO Auto-generated catch block 19 e.printStackTrace(); 20 } catch (RemoteException e) { 21 // TODO Auto-generated catch block 22 e.printStackTrace(); 23 } 24 } 25 26 }