• javaRMI远程调用


    什么是RMI

    Java RMI是专为Java环境设计的远程方法调用机制,是一种用于实现远程调用(RPC,Remote Procedure Call)的Java API,能直接传输序列化后的Java对象和分布式垃圾收集。它的实现依赖于JVM,因此它支持从一个JVM到另一个JVM的调用。

    在Java RMI中,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程方法,其中对象是通过序列化方式进行编码传输的。所以平时说的反序列化漏洞的利用经常是涉及到RMI,就是这个意思。

    RMI依赖的通信协议为JRMP(Java Remote Message Protocol,Java远程消息交换协议),该协议是为Java定制的,要求服务端与客户端都必须是Java编写的。

    RMI的模式与交互过程

    设计模式

    RMI的设计模式中,主要包括以下三个部分的角色:

    • Registry:提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。
    • Server:远程方法的提供者,并向Registry注册自身提供的服务
    • Client:远程方法的消费者,从Registry获取远程方法的相关信息并且调用

    交互过程

    1. 首先,启动RMI Registry服务,启动时可以指定服务监听的端口,也可以使用默认的端口(1099);
    2. 其次,Server端在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming/Context/Registry等类的bindrebind方法将刚才实例化好的实现类注册到RMI Registry上并对外暴露一个名称;
    3. 最后,Client端通过本地的接口和一个已知的名称(即RMI Registry暴露出的名称),使用RMI提供的Naming/Context/Registry等类的lookup方法从RMI Service那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,便可以实现远程调用对象的方法了;

    远程对象

    远程对象是存在于服务端以供客户端调用的对象。任何可以被远程调用的对象都必须实现 java.rmi.Remote 接口,远程对象的实现类必须继承UnicastRemoteObject类。如果不继承UnicastRemoteObject类,则需要手工初始化远程对象,在远程对象的构造方法的调用UnicastRemoteObject.exportObject()静态方法。这个远程对象中可能有很多个函数,但是只有在远程接口中声明的函数才能被远程调用,其他的公共函数只能在本地的JVM中使用。

    使用远程方法调用,必然会涉及参数的传递和执行结果的返回。参数或者返回值可以是基本数据类型,当然也有可能是对象的引用。所以这些需要被传输的对象必须可以被序列化,这要求相应的类必须实现java.io.Serializable接口,并且客户端的serialVersionUID字段要与服务器端保持一致。

    流程原理

    从RMI设计角度来讲,基本分为三层架构模式来实现RMI,分别为RMI服务端,RMI客户端和RMI注册中心。

    客户端

    存根/桩(Stub):远程对象在客户端上的代理;
    远程引用层(Remote Reference Layer):解析并执行远程引用协议;
    传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果

    服务端

    骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法, 并接收方法执行后的返回值;
    远程引用层(Remote Reference Layer):处理远程引用后向骨架发送远程方法调用;
    传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。

    注册表(Registry)

    以URL形式注册远程对象,并向客户端回复对远程对象的引用。

    流程原理图

    编写RMI步骤

    定义服务端供远程调用的类

    在此之前先定义一个可序列化的Model层的用户类,其实例可放置于服务端进行远程调用:

    import java.io.Serializable;
    
    public class PersonEntity implements Serializable {
        private int id;
        private String name;
        private int age;
    
        public void setId(int id) {
            this.id = id;
        }
    
        public int getId() {
            return id;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public int getAge() {
            return age;
        }
    }
    

    定义一个远程接口

    远程接口必须继承java.rmi.Remote接口,且抛出RemoteException错误:

    import java.rmi.Remote;
    import java.rmi.RemoteException;
    import java.util.List;
    
    public interface PersonService extends Remote {
        public List<PersonEntity> GetList() throws RemoteException;
    }
    

    开发接口的实现类

    建立PersonServiceImpl实现远程接口,注意此为远程对象实现类,需要继承UnicastRemoteObject(如果不继承UnicastRemoteObject类,则需要手工初始化远程对象,在远程对象的构造方法的调用UnicastRemoteObject.exportObject()静态方法):

    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    import java.util.LinkedList;
    import java.util.List;
    
    public class PersonServiceImpl extends UnicastRemoteObject implements PersonService {
        public PersonServiceImpl() throws RemoteException {
            super();
        // TODO Auto-generated constructor stub
        }
    
        @Override
        public List<PersonEntity> GetList() throws RemoteException {
        // TODO Auto-generated method stub
            System.out.println("Get Person Start!");
            List<PersonEntity> personList = new LinkedList<PersonEntity>();
    
            PersonEntity person1 = new PersonEntity();
            person1.setAge(3);
            person1.setId(0);
            person1.setName("small_fish");
            personList.add(person1);
    
            PersonEntity person2 = new PersonEntity();
            person2.setAge(18);
            person2.setId(1);
            person2.setName("lalalaxiaoyuren");
            personList.add(person2);
    
            return personList;
        }
    }
    
    

    创建Server和Registry

    其实Server和Registry可以单独运行创建,其中Registry可通过代码启动也可通过rmiregistry命令启动,这里只进行简单的演示,将Server和Registry的创建、对象绑定注册表等都写到一块,且Registry直接代码启动:

    import java.rmi.Naming;
    import java.rmi.registry.LocateRegistry;
    
    public class Program {
        public static void main(String[] args) {
            try {
                PersonService personService=new PersonServiceImpl();
                //注册通讯端口
                LocateRegistry.createRegistry(6600);
                //注册通讯路径
                Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService);
                System.out.println("Service Start!");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    

    创建客户端并查找调用远程方法

    这里我们通过Naming.lookup()来查找RMI Server端的远程对象并获取到本地客户端环境中输出出来:

    import java.rmi.Naming;
    import java.util.List;
    
    public class Client {
        public static void main(String[] args){
            try{
                //调用远程对象,注意RMI路径与接口必须与服务器配置一致
                PersonService personService=(PersonService) Naming.lookup("rmi://127.0.0.1:6600/PersonService");
                List<PersonEntity> personList=personService.GetList();
                for(PersonEntity person:personList){
                    System.out.println("ID:"+person.getId()+" Age:"+person.getAge()+" Name:"+person.getName());
                }
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
    

    函数说明

    • bind(String name, Object obj):注册对象,把对象和一个名字name绑定,这里的name其实就是URL格式。如果改名字已经与其他对象绑定,则抛出NameAlreadyBoundException错误;
    • rebind(String name, Object obj):注册对象,把对象和一个名字name绑定。如果改名字已经与其他对象绑定,不会抛出NameAlreadyBoundException错误,而是把当前参数obj指定的对象覆盖原先的对象;
    • lookup(String name):查找对象,返回与参数name指定的名字所绑定的对象;
    • unbind(String name):注销对象,取消对象与名字的绑定;
  • 相关阅读:
    正则表达式周二挑战赛 第七周
    [译]视区百分比,canvas.toBlob()以及WebRTC
    [译]因扩展Object.prototype而引发Object.defineProperty不可用的一个问题
    [译]JavaScript需要类吗?
    [译]JavaScript中几种愚蠢的写法
    [译]JavaScript中对象的属性
    JavaScript:数组的length属性
    [译]JavaScript中的变量声明:你可以打破的三条规则
    [译]ES6:JavaScript中将会有的几个新东西
    [译]ECMAScript 6中的集合类型,第三部分:WeakMap
  • 原文地址:https://www.cnblogs.com/lalalaxiaoyuren/p/15988320.html
Copyright © 2020-2023  润新知