RPC实践(一)起步从非RPC的RMI开始

来源:互联网 发布:sql查询学生总分 编辑:程序博客网 时间:2024/06/05 15:03

在工作中使用java编写服务的同学都有用过或者知道RMI,从RMI和RPC解决问题的出发点看,有一些是相同的,所以有相似之处,都提供了一种远程调用接口的能力,解决了分布式系统中相互之间调用的问题。不过他们之间还是有区别的。

一、RMI不是RPC

RMI是Java的一组开发分布式应用程序的API。RMI使用Java语言接口定义了远程对象,它集合了Java序列化和Java远程方法协议(Java Remote Method Protocol)。简单地说,这样使原先的程序在同一操作系统的方法调用,变成了不同操作系统之间程序的方法调用,由于J2EE是分布式程序平台,它以RMI机制实现程序组件在不同操作系统之间的通信。 但RMI的使用必须是在能够识别java代码的环境下使用,即必须有JVM的支持。因此,他只适合在java程序间的对象通信。如果不在 Java 环境下工作,或者需要与非 Java 环境通信,那么SOAP、RPC、CORAR等都是可以的。

      RPC(Remote Method Invocation,远端过程调用) 与RMI的区别很明显,相比于RMI直接获取远端方法的签名,进行调用的方式,RPC使用的是C/S方式,发送请求到服务器,等待服务器返回结果。为了包装RPC的请求信息,推出了XML-RPC,客户端发送一条特定消息,该消息中必须包括名称、运行服务的程序以及输入参数。XML-RPC只能使用有限的数据类型种类和一些简单的数据结构。SOAP最主要的工作是使用标准的XML描述了RPC的请求信息(URI/类/方法/参数/返回值)。SOAP的方式,SOAP 是对如CORBA 和 RMI-IIOP 这样的重型 范例吸引人的替代。

      RMI 与 RPC的相似性在于功能相似,我们可以从他们的功能图看他们的相似之处


从上面的功能框架看,RMI 和 RPC 实现的功能确实比较类似的, 当然RPC可以跨语言实现调用,功能上更为强大。如果从实现机制上看就会发现RMI 与 RPC 有很大不同。 RMI是基于类的远程调用方式,在两边有同一个类的主体和存根,而RPC 可以基于HTTP 或者别的通信协议,进行调用,在通信的数据包里,含有调用的方法,参数等序列化后的信息。

下面是RMI的机制原理图:


再来看一下RPC的机制原理:


RPC原理图与RMI原理图的最大区别在于,RMI是对象类的操作,客户端和服务端之间的通信协议已经由RMI自行解决。RPC调用,客户端与服务端之间传送的操作的数据内容,这些数据内容经过了序列化和反序列化的过程,这样的机制让RPC调用具有更为灵活的特性,因为只是通过通信(常用http,socket)协议来传送数据包,因此这种机制可以跨语言,跨平台,只要序列化和反序列化能够保证一致性,就可以实现灵活的调用。

二、RMI调用的简单例子

下面通过一个简单的例子来实践和体会RMI调用

2.1 远程对象接口和远程对象实现

对象实例:

package com.cwqsolo.rmidemo.enitity;import java.rmi.Remote;import java.rmi.RemoteException;/* * 首先定义一个继承Remote的接口 * 这个接口中定义的方法需要抛出RemoteException */public interface RmiEntityHello extends Remote {     /**      * 简单的方法,无需传入参数,只返回“Hello World!"      */     public String helloWorld() throws RemoteException;     /**      * 带参数的方法:输入参数为名字,返回“名字,Hello World!”       */     public String sayHello2Someone(String Name) throws RemoteException;     }
定义了一个继承remote的 接口,然后还需要定义个接口的实现类,并且这个实现类需要继承UnicastRemoteObject,否则客户端调用的都是本地而非服务端。这个接口的实现类如下:

package com.cwqsolo.rmidemo.enitity;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;public class RmiEntityHelloImpl extends UnicastRemoteObject implements RmiEntityHello {         /**      * 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,必须声明抛出RemoteException异常        */     public RmiEntityHelloImpl() throws RemoteException {     }     /**      * 简单的方法,无需传入参数,只返回“Hello World!"      */     public String helloWorld() throws RemoteException {         return "Hello World!";     }     /**      * 带参数的方法:输入参数为名字,返回“名字,Hello World!”       */       public String sayHello2Someone(String Name) throws RemoteException {         return "Hello->" + Name + "!";     } }

    2.2  服务端代码:

package com.cwqsolo.rmiserver;import java.net.MalformedURLException;import java.rmi.AlreadyBoundException;import java.rmi.Naming;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import com.cwqsolo.rmidemo.enitity.RmiEntityHello;import com.cwqsolo.rmidemo.enitity.RmiEntityHelloImpl;public class HelloServer {        public static void main(String args[]) {         try {                         //创建一个远程对象 ,注意对象是接口继承类            RmiEntityHello myhello = new RmiEntityHelloImpl();                         //本地主机上的远程对象注册实例,并指定端口为5099,Java默认端口是1099)            //缺少注册创建,则无法绑定对象到远程注册表上             LocateRegistry.createRegistry(5099);             //把远程对象注册到RMI注册服务器上,并命名为Hello             //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略)             Naming.bind("rmi://localhost:5099/Hello", myhello );             System.out.println(">>>>>INFO:远程RmiEntityHello对象绑定成功!");                     } catch (RemoteException e) {             System.out.println("创建远程对象发生异常!");             e.printStackTrace();         } catch (AlreadyBoundException e) {             System.out.println("发生重复绑定对象异常!");             e.printStackTrace();         } catch (MalformedURLException e) {             System.out.println("发生URL畸形异常!");             e.printStackTrace();         }     } }

 2.3 客户端代码

package com.cwqsolo.rmidemo.client;import java.net.MalformedURLException;import java.rmi.Naming;import java.rmi.NotBoundException;import java.rmi.RemoteException;import com.cwqsolo.rmidemo.enitity.RmiEntityHello;public class HelloClient {            public static void main(String args[]){         try {             //在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法             RmiEntityHello myhello =(RmiEntityHello) Naming.lookup("rmi://localhost:5099/Hello");             System.out.println(myhello.helloWorld());             System.out.println(myhello.sayHello2Someone( "Honey") );         } catch (NotBoundException e) {             e.printStackTrace();         } catch (MalformedURLException e) {             e.printStackTrace();         } catch (RemoteException e) {             e.printStackTrace();           }     } }

三:例子运行情况

首先启动服务端,服务端启动后,对端口进行监听,等待客户端连接

然后客户端连接并查询远程对象

最后调用远程对象的方法

服务端日志:

>>>>>INFO:远程RmiEntityHello对象绑定成功!

客户端日志:

Hello World!
       Hello->Honey!

四:java rmi机制的一些实现

(这里的内容参考网络)

4.1:UnicastRemoteObject.java

基类的构造方法将远程对象发布到一个随机端口上,构造函数

 protected UnicastRemoteObject(int port) throws RemoteException    {        this.port = port;        exportObject((Remote) this, port);    }    public static Remote exportObject(Remote obj, int port)  throws RemoteException    {        return exportObject(obj, new UnicastServerRef(port));    }    private static Remote exportObject(Remote obj, UnicastServerRef sref)  throws RemoteException    {        // if obj extends UnicastRemoteObject, set its ref.        if (obj instanceof UnicastRemoteObject) {            ((UnicastRemoteObject) obj).ref = sref;        }        return sref.exportObject(obj, null, false);    }

     4.2 UnicastServerRef.java

 public Remote exportObject(Remote impl, Object data, boolean permanent) throws RemoteException    {        Class implClass = impl.getClass();        Remote stub;         try {            stub = Util.createProxy(implClass, getClientRef(), forceStubUse);        } catch (IllegalArgumentException e) {            throw new ExportException(                "remote object implements illegal remote interface", e);        }        if (stub instanceof RemoteStub) {            setSkeleton(impl);        }         Target target =            new Target(impl, this, stub, ref.getObjID(), permanent);        ref.exportObject(target);        hashToMethod_Map = hashToMethod_Maps.get(implClass);        return stub;    }
   这里的stub变量就是我们lookup出来的远程对象的存根对象。而target保留了远程对象的信息集合:对象,存根,objId等。

     4.3 TCPTransport.java

public void exportObject(Target target) throws RemoteException {        /*         * Ensure that a server socket is listening, and count this         * export while synchronized to prevent the server socket from         * being closed due to concurrent unexports.         */        synchronized (this) {            listen();            exportCount++;        }         /*         * Try to add the Target to the exported object table; keep         * counting this export (to keep server socket open) only if         * that succeeds.         */        boolean ok = false;        try {            super.exportObject(target);            ok = true;        } finally {            if (!ok) {                synchronized (this) {                    decrementExportCount();                }            }        }    }
     listen()启动监听。这里对TcpTransport加了同步,防止多个线程同时执行,同时也防止同一端口启动多次。一次TcpTransport会有一个监听端口,而一个端口上可能会发部多个远程对象。exportCount计录该TcpTransport发布了多少个对象。

  public void exportObject(Target target) throws RemoteException {        target.setExportedTransport(this);        ObjectTable.putTarget(target);    }
ObjectTable为静态方法调用,那么所有的远程对象信息(target)都会放到这个对象表中。我们这个target包含了远程对象几乎所有的信息,找到他,就找到远程对象的存根对象。

    4.4  bind   (RegistryImpl.java )

public void bind(String name, Remote obj)        throws RemoteException, AlreadyBoundException, AccessException    {        checkAccess("Registry.bind");        synchronized (bindings) {            Remote curr = bindings.get(name);            if (curr != null)                throw new AlreadyBoundException(name);            bindings.put(name, obj);        }    }

      4.5 RegistryImpl_Skel.class

case 2: // lookup(String)    {        java.lang.String $param_String_1;        try {        java.io.ObjectInput in = call.getInputStream();        $param_String_1 = (java.lang.String) in.readObject();        } catch (java.io.IOException e) {        throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);        } catch (java.lang.ClassNotFoundException e) {        throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);        } finally {        call.releaseInputStream();        }        java.rmi.Remote $result = server.lookup($param_String_1);        try {        java.io.ObjectOutput out = call.getResultStream(true);        out.writeObject($result);        } catch (java.io.IOException e) {        throw new java.rmi.MarshalException("error marshalling return", e);        }        break;    }

     4.6 MarshalOutputStream.java

protected final Object replaceObject(Object obj) throws IOException {        if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) {            Target target = ObjectTable.getTarget((Remote) obj);            if (target != null) {                return target.getStub();            }        }        return obj;    }



附:执行10000次2.174 seconds  ,每秒4600次。






0 0
原创粉丝点击