RPC入门总结(二)RMI的原理和使用

转载:架构设计:系统间通信(8)——通信管理与RMI 上篇

转载:JAVA RMI分布式原理和应用

转载:

一、RMI

RMI(Remote Method Invocation,远程方法调用),是JAVA早在JDK 1.1中提供的JVM与JVM之间进行 对象方法调用的技术框架的实现(在JDK的后续版本中,又进行了改进)。通过RMI技术,某一个本地的JVM可以调用存在于另外一个JVM中的对象方法,就好像它仅仅是在调用本地JVM中某个对象方法一样。例如RMI客户端中的如下调用:

List< UserInfo > users = remoteServiceInterface.queryAllUserinfo();
看似remoteServiceInterface对象和普通的对象没有区别,但实际上remoteServiceInterface对象的具体方法实现却不在本地的JVM中,而是在某个远程的JVM中(这个远程的JVM可以是RMI客户端同属于一台物理机,也可以属于不同的物理机)

二、RMI的适用场景

RMI是基于JAVA语言的,也就是说在RMI技术框架的描述中,只有Server端使用的是JAVA语言并且Client端也是用的JAVA语言,才能使用RMI技术(目前在codeproject.com中有一个开源项目名字叫做“RMI for C++”,可以实现JAVA To C++的RMI调用。但是这是一个第三方的实现,并不是java的标准RMI框架定义,所以并不在我们的讨论范围中)。RMI适用于两个系统都主要使用JAVA语言进行构造,不需要考虑跨语言支持的情况。并且对两个JAVA系统的通讯速度有要求的情况。

RMI 是一个良好的、特殊的RPC实现:使用JRMP协议承载数据描述,可以使用BIO和NIO两种IO通信模型。RMI框架是可以在大规模集群系统中使用的,当然是不是使用RMI技术,还要看您的产品的技术背景、团队的技术背景、公司的业务背景甚至客户的非技术背景等。

三、RMI的基本框架

从设计角度上讲,JAVA采用的是三层结构模式来实现RMI。在整个体系结构中,有如下几个关键角色构成了通信双方:
        1.客户端:
           1)桩(StubObject):远程对象在客户端上的代理;
           2)远程引用层(RemoteReference Layer):解析并执行远程引用协议;
           3)传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果。
        2.服务端:
           1)骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值;
           2)远程引用层(Remote ReferenceLayer):处理远程引用语法之后向骨架发送远程方法调用;
           3)传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。
        3.注册表(Registry):以URL形式注册远程对象,并向客户端回复对远程对象的引用。

 
        在实际的应用中,客户端并没有真正的和服务端直接对话来进行远程调用,而是通过本地JVM环境下的 桩对象来进行的。
远程调用过程:
        1)客户端从远程服务器的注册表中查询并 获取远程对象引用。当进行远程调用时,客户端首先会与桩对象(Stub Object)进行对话,而这个桩对象将远程方法所需的参数进行序列化后,传递给它下层的远程引用层( RRL);
        2)桩对象与远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的 桩对象代理完成的。远程引用层在将桩的本地引用转换为服务器上对象的远程引用后,再将调用传递给传输层(Transport),由传输层通过 TCP协议发送调用;      
        3)在服务器端,传输层监听入站连接,它一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层;
        4)服务器端的远程引用层将客户端发送的远程应用转换为本地虚拟机的引用后,再将请求传递给骨架(Skeleton);
        5)骨架读取参数,又将请求传递给服务器,最后由服务器进行实际的方法调用。
结果返回过程:
        1)如果远程方法调用后有返回值,则服务器将这些结果又沿着“ 骨架->远程引用层->传输层”向下传递;
        2)客户端的传输层接收到返回值后,又沿着“ 传输层->远程引用层->桩”向上传递,然后由桩来反序列化这些返回值,并将最终的结果传递给客户端程序。

这里写图片描述

从技术的角度上讲,有如下几个主要类或接口扮演着上述三层模型中的关键角色:
        1)注册表:java.rmi.Namingjava.rmi.Registry
        2)骨架:java.rmi.remote.Skeleton        
        3)桩:java.rmi.server.RemoteStub
        4)远程引用层:java.rmi.server.RemoteRefsun.rmi.transport.Endpoint
        5)传输层:sun.rmi.transport.Transport



四、RMI的开发流程

作为一般的RMI应用,JAVA为我们隐藏了其中的处理细节,而让开发者有更多的精力和时间花在实际的应用中。开发RMI的步骤如下所述: 
1.服务端
        1)定义Remote子接口,在其内部定义要发布的远程方法,并且这些方法都要Throws RemoteException
        2)定义远程对象的实现类,通常有两种方式:
              a. 继承UnicastRemoteObjectActivatable,并同时实现Remote子接口; 
              b. 只实现Remote子接口和java.io.Serializable接口。 
        3)编译桩(在JAVA 1.5及以后版本中,如果远程对象实现类继承了UnicastRemoteObjectActivatable,则无需此步,由JVM自动完成。否则需手工利用RMIC工具编译生成此实现类对应的桩类,并放到和实现类相同的编译目录下); 
        4)启动服务器:依次完成注册表的启动远程对象绑定。另外,如果远程对象实现类在定义时没有继承UnicastRemoteObject或Activatable,则必须在服务器端显式的调用UnicastRemoteObject类中某个重载的exportObject(Remote remote)静态方法,将此实现类对象导出成为一个真正的远程对象。 
2.客户端: 
       1)定义用于接收远程对象的Remote子接口,只需实现java.rmi.Remote接口即可。但要求必须与服务器端对等的Remote子接口保持一致,即有相同的接口名称、包路径和方法列表等。
       2)通过符合JRMP规范的URL字符串在注册表中获取并强转成Remote子接口对象
       3)调用这个Remote子接口对象中的某个方法就是为一次远程方法调用行为。

下面结合一个例子来说明RMI分布式应用的开发步骤。
        背景:远程系统管理接口SystemManager允许客户端远程调用其内部的某个方法,来获取服务器环境下某个属性的值。因此,第一步需要在服务端和与之通信的客户端环境下,定义一个完全一样的SystemManager接口,将此接口标记为远程对象。

1.在服务端和客户端定义对等Remote子接口(SystemManager)

/**
 * 为代码可读性考虑省略了引用源码中的作者信息如下    
 * @author  <a href="mailto:code727@gmail.com">Daniele</a> 
 * @version 1.0.0, 2013-5-21 
 * @see     
 * @since   AppDemo1.0.0 
 */

package com.daniele.appdemo.rmi;  
  
import java.rmi.Remote;  
import java.rmi.RemoteException;  
  
import com.daniele.appdemo.test.domain.User;  
  
/** 
 * 系统管理远程对象接口
 */  
public interface SystemManager extends Remote {  
     
    /** 
     * 发布的远程对象方法一:获取所有系统环境信息 
     */  
    public String getAllSystemMessage() throws RemoteException;  
     
    /** 
     * 发布的远程对象方法二:获取指定的系统环境信息 
     */  
    public String getSystemMessage(String properties) throws RemoteException;  
}  

2.在服务端定义Remote子接口的实现类(SystemManagerImpl),即远程对象的实际行为逻辑

系统管理远程对象实现类。有两种实现方式: 
 *      1.继承UnicastRemoteObjectActivatable,并同时实现Remote子接口 
 *      2.只实现Remote子接口,这种方式灵活但比较复杂: 
 *        1)要求此实现类必须实现java.io.Serializable接口; 
 *        2)通过这种方式定义的实现类此时还不能叫做远程对象实现类,因为在服务器端绑定远程对象之前,还需要利用JDK提供的rmic工具将此实现类手工编译生成对应的桩实现类,并放到和它相同的编译目录下。 


package com.daniele.appdemo.rmi.server;  
  
import java.io.Serializable;  
import java.rmi.RemoteException;  
import java.rmi.server.RemoteServer;  
import java.rmi.server.ServerNotActiveException;  
  
import org.apache.log4j.Logger;  
  
import com.daniele.appdemo.rmi.SystemManager;  
import com.daniele.appdemo.test.domain.User;  
import com.daniele.appdemo.util.SystemUtils;  

public class SystemManagerImpl implements SystemManager, Serializable {  
     
//public class SystemManagerImpl extends UnicastRemoteObject implements SystemManager {  
     
    private static final long serialVersionUID = 9128780104194876777L;  
    private static final Logger logger = Logger.getLogger(SystemManagerImpl.class);  
    private static SystemManagerImpl systemManager = null;  
  
    /** 
     * 在服务端本地的匿名端口上创建一个用于监听目的的UnicastRemoteObject对象 
     */  
    private SystemManagerImpl() throws RemoteException {  
        super();  
        // 在控制台中显示远程对象被调用,以及返回结果时产生的日志  
        RemoteServer.setLog(System.out);  
    }  
     
    public static SystemManagerImpl getInstance() throws RemoteException {  
        if (systemManager == null) {  
            synchronized (SystemManagerImpl.class) {  
                if (systemManager == null)  
                    systemManager = new SystemManagerImpl();  
            }  
        }  
        return systemManager;  
    }  
  
    public String getAllSystemMessage() throws RemoteException {  
        try {  
            /* 
             * getClientHost()方法可以获取触发当前远程方法被调用时的客户端的主机名。 
             * 在远程服务端的环境中,如果当前线程实际上没有运行客户端希望调用的远程方法时, 
             * 则会抛出ServerNotActiveException。 
             * 因此,为了尽量避免这个异常的发生,它通常用于远程方法的内部实现逻辑中, 
             * 以便当此方法真正的被调用时,可以记录下哪个客户端在什么时间调用了这个方法。 
             */  
                logger.info("Client {" + RemoteServer.getClientHost() + "} invoke method [getAllSystemMessage()]" );  
            } catch (ServerNotActiveException e) {  
                e.printStackTrace();  
            }  
        return SystemUtils.formatSystemProperties();  
    }  
  
    public String getSystemMessage(String properties) throws RemoteException {  
        try {  
            logger.info("Client {"  
                    + RemoteServer.getClientHost()  
                    + "} invoke method [getAllSystemMessage(String properties)]");  
             } catch (ServerNotActiveException e) {  
                e.printStackTrace();  
            }  
        return SystemUtils.formatSystemProperties(properties.split(","));  
    }  
  
}  

3.编译生成打桩类

由于SystemManagerImpl 不是通过继承UnicastRemoteObject 或 Activatable来实现的,因此在服务器端需要利用JDK提供的rmic工具编译生成对应的桩类。否则,此步略过。例如,在Windows环境下打开命令控制台后  
1)进入工程根目录 :   
         cd d:\ Development\AppDemo
2)桩编译: 
         rmic -classpath WebContent\WEB-INF\classes com.daniele.appdemo.rmi.server.SystemManagerImpl
   语法格式为:
    rmic -classpath <远程对象实现类bin目录的相对路径> <远程对象实现类所在的包路径>.<远程对象实现类的名称>
完成后,rmic将在相对于根目录的com\daniele\appdemo\rmi\server子目录中自动生成SystemManagerImpl_Stub桩对象类 (即“远程对象实现类名称_Stub”) 的编译文件,此时需要再将此编译文件拷贝到与远程对象实现类SystemManagerImpl相同的编译目录(\WebContent\WEB-INF\classes\com\daniele\appdemo\rmi\server)中,否则在服务器端发布远程对象时将会抛出java.rmi.StubNotFoundException。如下图所示。

需要特别注意的是:如果服务端中的远程对象实现类存在有对应的桩对象类编译文件,则要求在RMI客户端的环境中,也必须有这个对等的桩对象类编译文件,即意味着这个文件在两端有着相同的包路径、文件名和内部实现细节。因此,最简单的做法就是连同整个包(com\daniele\appdemo\rmi\server)在内,将图2中的SystemManagerImpl_Stub.class文件拷贝到RMI客户端工程的bin目录下即可。如下图。否则,当RMI客户端调用远程服务时将会抛出java.rmi.StubNotFoundException。


4.创建用于发布远程对象目的用的服务器(SystemManagerServer)

package com.daniele.appdemo.rmi.server;  
  
import java.io.IOException;  
import java.util.Arrays;   
import java.rmi.Naming;  
import java.rmi.registry.LocateRegistry;  
import java.rmi.server.UnicastRemoteObject;  
  
import org.apache.log4j.Logger;  
  
import com.daniele.appdemo.rmi.SystemManager;  
  
/** 
 *   系统管理远程服务器,它主要完成如下任务: 
 *     1.在绑定之前先启动注册表服务。 
 *     2.将远程对象SystemManager绑定到注册表中,以便让客户端能远程调用这个对象所发布的方法; 
 */  
public class SystemManagerServer {  
      
    private static final Logger logger = Logger.getLogger(SystemManagerServer.class);  
      
    public static void main(String[] args) {  
        try {  
              
                SystemManager systemManager = SystemManagerImpl.getInstance();  
              
                /* 
                 *  如果远程对象实现类不是通过继承UnicastRemoteObject或Activatable来定义的, 
                 *  则必须在服务器端显示的调用UnicastRemoteObject类中某个重载的exportObject(Remote remote)静态方法, 
                 *  将此实现类的某个对象导出成为一个真正的远程对象。否则,此步省略。 
                 */  
                UnicastRemoteObject.exportObject(systemManager);  
                 
                int port = 9527;  
                String url = "rmi://localhost:" + port + "/";  
                String remoteObjectName = "systemManager";  
  
                /* 
                 *  在服务器的指定端口(默认为1099)上启动RMI注册表。 
                 *  必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上。 
                 */  
                LocateRegistry.createRegistry(port);  
              
                /* 
                 *  将指定的远程对象绑定到注册表中: 
                 *  1.如果端口号不为默认的1099,则绑定时远程对象名称中必须包含Schema, 
                 *    即"rmi://<host或ip><:port>/"部分,并且Schema里指定的端口号应与createRegistry()方法参数中的保持一致 
                 *  2.如果端口号为RMI默认的1099,则远程对象名称中不用包含Schema部分,直接定义名称即可 
                 */  
                if (port == 1099)  
                    Naming.rebind(remoteObjectName, systemManager);  
                else  
                    Naming.rebind(url + remoteObjectName, systemManager);  
               logger.info("Success bind remote object " + Arrays.toString(Naming.list(url)));  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
      }  
      
}   

此处需要注意:Registry所在的JVM可以和Service提供者的JVM不相同,即可以专门使用一个JVM作为服务注册中心使用,此时的代码演变如下:

package testRMI;


import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RemoteRegistryUnicastMain {
    public static void main(String[] args) throws Exception {
        /*
         * 我们通过LocateRegistry的get方法,寻找一个存在于远程JVM上的RMI注册表
         * */
        Registry registry = LocateRegistry.getRegistry("192.168.61.1", 1099);


        // 以下是向远程RMI注册表(绑定/重绑定)RMI Server的Stub。
        // 同样的远程RMI注册表的JVM-classpath下,一定要有这个RMI Server的Stub
        RemoteUnicastServiceImpl remoteService = new RemoteUnicastServiceImpl();

        /*
         * 
         * 注册的服务仓库存在于192.168.61.1这个IP上 
         * 使用注册表registry进行绑定或者重绑定时,不需要写完整的RMI URL
         * */
        registry.bind("queryAllUserinfo" , remoteService);
    }
}
使用getRegistry函数可以获取远程RMI注册表,之后的工作与本地注册表没啥两样。

5.创建发出远程调用请求的客户端(SystemManagerClient)

package com.daniele.appdemo.rmi.client;  
  
import java.io.IOException;  
import java.rmi.Naming;  
import java.rmi.NotBoundException;  
  
import com.daniele.appdemo.rmi.SystemManager;  
import com.daniele.appdemo.test.domain.User;  
  
/** 
 * 系统管理进行远程调用的客户端  
 */  
  
public class SystemManagerClient {  
  
    public static void main(String[] args) {  
        /* 
         * RMI URL格式:Schame/<远程对象名> 
         * 1.Schame部分由"rmi://<server host或IP>[:port]"组成, 
         *   如果远程对象绑定在服务端的1099端口(默认)上,则port部分可以省略,否则必须指定。 
         * 2.URL最后一个"/"后面的值为远程对象绑定在服务端注册表上时定义的远程对象名, 
         *   即对应Naming.rebind()方法第一个参数值中最后一个"/"后面的值 
         */  
        String rmi = "rmi://192.168.1.101:9527/systemManager";  
        try {  
                /* 
                 * 根据URL字符串查询并获取远程服务端注册表中注册的远程对象, 
                 * 这里返回的是本地实现了Remote接口的子接口对象(Stub), 
                 * 它与服务端中的远程对象具有相同的接口和方法列表,因而作为在客户端中远程对象的一个代理。 
                 */  
                SystemManager systemManager = (SystemManager) Naming.lookup(rmi);  
           
//              System.out.println(systemManager.getAllSystemMessage());  
                System.out.println(systemManager.getSystemMessage("java.version,os.name"));  
            } catch (IOException e) {  
                e.printStackTrace();  
            } catch (NotBoundException e) {  
                e.printStackTrace();  
            }  
    }  
  
}  
完成后,再依次到服务器和客户端上启动SystemManagerServer和SystemManagerClient即可。

五、RMI的源码解析

1. 服务端输出远程对象

Server端调用UnicastRemoteObject的export方法输出远程对象,export方法会在一个线程里监听某个TCP端口上的方法调用请求:

public void exportObject(Target target) throws RemoteException {  
    // other code  
    while (true) {  
        ServerSocket myServer = server;  
        if (myServer == null)  
          return;  
        Throwable acceptFailure = null;  
        final Socket socket;  
          
        try {  
            socket = myServer.accept();  
            /* 
             * Find client host name (or "0.0.0.0" if unknown) 
             */  
            InetAddress clientAddr = socket.getInetAddress();  
            String clientHost = (clientAddr != null  
                         ? clientAddr.getHostAddress()  
                         : "0.0.0.0");  
            /* 
             * Spawn non-system thread to handle the connection 
             */  
            Thread t = (Thread) java.security.AccessController.doPrivileged (  
                    new NewThreadAction(new ConnectionHandler(socket,clientHost),  
                            "TCP Connection(" + ++ threadNum + ")-" + clientHost,  
                            true, true));  
            t.start();
        } catch (IOException e) {  
            acceptFailure = e;  
        } catch (RuntimeException e) {  
            acceptFailure = e;  
        } catch (Error e) {  
            acceptFailure = e;  
        }  
    }  
    // other code  
}
Server端就是在ServerSocket的accept方法上面监听到来的请求,如果有新的方法调用请求到来,Server产生一个单独的线程来处理新接收的请求:

public void dispatch(Remote obj, RemoteCall call) throws IOException {  
    // positive operation number in 1.1 stubs;  
    // negative version number in 1.2 stubs and beyond...  
    int num;  
    long op;  
    try {  
        // read remote call header  
        ObjectInput in;  
        try {  
            in = call.getInputStream();  
            num = in.readInt();  
            if (num >= 0) {  
                if (skel != null) {  
                oldDispatch(obj, call, num);  
                return;  
                } else {  
                    throw new UnmarshalException(  
                        "skeleton class not found but required " +  
                        "for client version");  
                }  
            }  
            op = in.readLong();  
        } catch (Exception readEx) {  
            throw new UnmarshalException("error unmarshalling call header",  
                             readEx);  
        }  
        /* 
         * Since only system classes (with null class loaders) will be on 
         * the execution stack during parameter unmarshalling for the 1.2 
         * stub protocol, tell the MarshalInputStream not to bother trying 
         * to resolve classes using its superclasses's default method of 
         * consulting the first non-null class loader on the stack. 
         */  
        MarshalInputStream marshalStream = (MarshalInputStream) in;  
        marshalStream.skipDefaultResolveClass();  
        Method method = (Method) hashToMethod_Map.get(new Long(op));  
        if (method == null) {  
            throw new UnmarshalException("invalid method hash");  
        }  
        // if calls are being logged, write out object id and operation  
        logCall(obj, method);  
        // unmarshal parameters  
        Class[] types = method.getParameterTypes();  
        Object[] params = new Object[types.length];  
        try {  
            unmarshalCustomCallData(in);  
            for (int i = 0; i < types.length; i++) {  
                params[i] = unmarshalValue(types[i], in);  
            }  
        } catch (java.io.IOException e) {  
            throw new UnmarshalException(  
                "error unmarshalling arguments", e);  
        } catch (ClassNotFoundException e) {  
            throw new UnmarshalException(  
                "error unmarshalling arguments", e);  
        } finally {  
            call.releaseInputStream();  
        }  
        // make upcall on remote object  
        Object result;  
        try {  
            result = method.invoke(obj, params);  
        } catch (InvocationTargetException e) {  
            throw e.getTargetException();  
        }  
        // marshal return value  
        try {  
            ObjectOutput out = call.getResultStream(true);  
            Class rtype = method.getReturnType();  
            if (rtype != void.class) {  
                marshalValue(rtype, result, out);  
            }  
        } catch (IOException ex) {  
        throw new MarshalException("error marshalling return", ex);  
        /* 
         * This throw is problematic because when it is caught below, 
         * we attempt to marshal it back to the client, but at this 
         * point, a "normal return" has already been indicated, 
         * so marshalling an exception will corrupt the stream. 
         * This was the case with skeletons as well; there is no 
         * immediately obvious solution without a protocol change. 
         */  
        }  
    } catch (Throwable e) {  
        logCallException(e);  
          
        ObjectOutput out = call.getResultStream(false);  
        if (e instanceof Error) {  
            e = new ServerError(  
                "Error occurred in server thread", (Error) e);  
        } else if (e instanceof RemoteException) {  
            e = new ServerException(  
                "RemoteException occurred in server thread",  
                (Exception) e);  
        }  
        if (suppressStackTraces) {  
            clearStackTraces(e);  
        }  
        out.writeObject(e);  
    } finally {  
        call.releaseInputStream(); // in case skeleton doesn't  
        call.releaseOutputStream();  
    }  
}  

dispatch方法处理接收的请求,先从输入流中读取方法的编号来获得方法名称:op = in.readLong(),然后读取方法的所有参数:params[i] = unmarshalValue(types[i], in),接着就可以执行方法调用了:result = method. invoke(obj, params),最后把方法执行结果写入到输出流中: marshalValue(rtype, result, out)。一个方法调用就执行完成了。

2. 客户端发起调用请求

Client端请求一个远程方法调用时,先建立连接:Connection conn = ref.getChannel().newConnection(),然后发送方法参数: marshalValue(types[i], params[i], out),再发送执行方法请求:call.executeCall(),最后得到方法的执行结果:Object returnValue = unmarshalValue(rtype, in),并关闭接:ref.getChannel().free(conn, true)。

public Object invoke(Remote obj, java.lang.reflect.Method method, Object[] params, long opnum) throws Exception {  
    if (clientRefLog.isLoggable(Log.VERBOSE)) {  
        clientRefLog.log(Log.VERBOSE, "method: " + method);  
    }  
    if (clientCallLog.isLoggable(Log.VERBOSE)) {  
        logClientCall(obj, method);  
    }  
    Connection conn = ref.getChannel().newConnection();  
    RemoteCall call = null;  
    boolean reuse = true;  
    /* If the call connection is "reused" early, remember not to 
     * reuse again. 
     */  
    boolean alreadyFreed = false;  
    try {  
        if (clientRefLog.isLoggable(Log.VERBOSE)) {  
        clientRefLog.log(Log.VERBOSE, "opnum = " + opnum);  
        }  
        // create call context  
        call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);  
        // marshal parameters  
        try {  
            ObjectOutput out = call.getOutputStream();  
            marshalCustomCallData(out);  
            Class[] types = method.getParameterTypes();  
            for (int i = 0; i < types.length; i++) {   
                marshalValue(types[i], params[i], out);  
            }  
        } catch (IOException e) {  
            clientRefLog.log(Log.BRIEF,  
                "IOException marshalling arguments: ", e);  
            throw new MarshalException("error marshalling arguments", e);  
        }  
        // unmarshal return  
        call.executeCall();  
        try {  
            Class rtype = method.getReturnType();  
            if (rtype == void.class)  
                return null;  
            ObjectInput in = call.getInputStream();  
              
            /* StreamRemoteCall.done() does not actually make use 
             * of conn, therefore it is safe to reuse this 
             * connection before the dirty call is sent for 
             * registered refs.   
             */  
            Object returnValue = unmarshalValue(rtype, in);  
            /* we are freeing the connection now, do not free 
             * again or reuse. 
             */  
            alreadyFreed = true;  
            /* if we got to this point, reuse must have been true. */  
            clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");  
            /* Free the call's connection early. */  
            ref.getChannel().free(conn, true);  
            return returnValue;  
              
        } catch (IOException e) {  
            clientRefLog.log(Log.BRIEF,  
                     "IOException unmarshalling return: ", e);  
            throw new UnmarshalException("error unmarshalling return", e);  
        } catch (ClassNotFoundException e) {  
            clientRefLog.log(Log.BRIEF,  
                "ClassNotFoundException unmarshalling return: ", e);  
            throw new UnmarshalException("error unmarshalling return", e);  
        } finally {  
            try {  
                call.done();  
            } catch (IOException e) {  
                /* WARNING: If the conn has been reused early, 
                 * then it is too late to recover from thrown 
                 * IOExceptions caught here. This code is relying 
                 * on StreamRemoteCall.done() not actually 
                 * throwing IOExceptions.   
                 */  
                reuse = false;  
            }  
        }  
    } catch (RuntimeException e) {  
        /* 
         * Need to distinguish between client (generated by the 
         * invoke method itself) and server RuntimeExceptions. 
         * Client side RuntimeExceptions are likely to have 
         * corrupted the call connection and those from the server 
         * are not likely to have done so.  If the exception came 
         * from the server the call connection should be reused. 
         */  
        if ((call == null) ||   
        (((StreamRemoteCall) call).getServerException() != e)) {  
            reuse = false;  
        }  
        throw e;  
    } catch (RemoteException e) {  
        /* 
         * Some failure during call; assume connection cannot 
         * be reused.  Must assume failure even if ServerException 
         * or ServerError occurs since these failures can happen 
         * during parameter deserialization which would leave 
         * the connection in a corrupted state. 
         */  
        reuse = false;  
        throw e;  
    } catch (Error e) {  
        /* If errors occurred, the connection is most likely not 
                *  reusable.  
         */  
        reuse = false;  
        throw e;  
    } finally {  
        /* alreadyFreed ensures that we do not log a reuse that 
         * may have already happened. 
         */  
        if (!alreadyFreed) {  
        if (clientRefLog.isLoggable(Log.BRIEF)) {  
            clientRefLog.log(Log.BRIEF, "free connection (reuse = " +  
                       reuse + ")");  
        }  
        ref.getChannel().free(conn, reuse);  
        }  
    }  
}  


3. I/O模型

现在的rmi实现采用的io是bio,并没有采用jdk1.4提供的nio功能。JDK8版本及以前版本,RMI采用的IO模型是BIO

4. 线程模型

 在JDK1.5及以前版本中,RMI每接收一个远程方法调用就生成一个单独的线程来处理这个请求,请求处理完成后,这个线程就会释放:

Thread t = (Thread)java.security.AccessController.doPrivileged (  
                new NewThreadAction(new ConnectionHandler(socket,clientHost),  
                "TCP Connection(" + ++ threadNum +  ")-" + clientHost, true, true));  

在JDK1.6之后,RMI使用 线程池来处理新接收的远程方法调用请求- ThreadPoolExecutor

在JDK1.6中,RMI提供了可配置的线程池参数属性:
sun.rmi.transport.tcp.maxConnectionThread - 线程池中的最大线程数量
sun.rmi.transport.tcp.threadKeepAliveTime -   线程池中空闲的线程存活时间

5. Marshell和unMarshell

从客户端发送数据,到拿到service实例的过程,其实就是 对象的序列号–>网络传输–>反序列化的过程。

marshal 和 unmarshal的意思,其实就是 serialize和 unserialize的意思

protected static void marshalValue(Class type, Object value, ObjectOutput out)  throws IOException {  
    if (type.isPrimitive()) {  
        if (type == int.class) {  
        out.writeInt(((Integer) value).intValue());  
        } else if (type == boolean.class) {  
        out.writeBoolean(((Boolean) value).booleanValue());  
        } else if (type == byte.class) {  
        out.writeByte(((Byte) value).byteValue());  
        } else if (type == char.class) {  
        out.writeChar(((Character) value).charValue());  
        } else if (type == short.class) {  
        out.writeShort(((Short) value).shortValue());  
        } else if (type == long.class) {  
        out.writeLong(((Long) value).longValue());  
        } else if (type == float.class) {  
        out.writeFloat(((Float) value).floatValue());  
        } else if (type == double.class) {  
        out.writeDouble(((Double) value).doubleValue());  
        } else {  
        throw new Error("Unrecognized primitive type: " + type);  
        }  
    } else {  
        out.writeObject(value);  
    }  
}  
rotected static Object unmarshalValue(Class type, ObjectInput in) throws IOException, ClassNotFoundException {  
    if (type.isPrimitive()) {  
        if (type == int.class) {  
        return new Integer(in.readInt());  
        } else if (type == boolean.class) {  
        return new Boolean(in.readBoolean());  
        } else if (type == byte.class) {  
        return new Byte(in.readByte());  
        } else if (type == char.class) {  
        return new Character(in.readChar());  
        } else if (type == short.class) {  
        return new Short(in.readShort());  
        } else if (type == long.class) {  
        return new Long(in.readLong());  
        } else if (type == float.class) {  
        return new Float(in.readFloat());  
        } else if (type == double.class) {  
        return new Double(in.readDouble());  
        } else {  
        throw new Error("Unrecognized primitive type: " + type);  
        }  
    } else {  
        return in.readObject();  
    }  
}  
6. 远程对象

实现类继承UnicastRemoteObject时,lookup出来的对象类型是$Proxy0,而不继承UnicastRemoteObject的类(对象类型是com.guojje.Hello)。
我们把继承UnicastRemoteObject类的对象叫做远程对象,我们lookup出来的对象,只是该远程对象的存根(Stub)对象,而远程对象永远在服务端。客户端每一次的方法调用,最后都是仅有的那一个远程对象的方法调用
没有继承UnicastRemoteObject类的对象,同样可以bind到Registry,但lookup出来了对象却是远程对象经过序列化,然后到客户端反序列化出来的新的对象,后续的方法调用与远程对象再无关系


















  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值