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

1 篇文章 0 订阅

在工作中使用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
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值