Java安全之RMI篇(1)

学习学习

Posted by lll-yz on November 19, 2021

RCP

直接看RMI感觉有点吃力,所以来看看RPC先预热一下。

RPC(Remote Procedure Call) 远程过程调用,这只是一个统称,重点在于方法调用(不支持对象的概念),具体实现甚至可以用RMI、RestFul等去实现,但一般不用,因为RMI不能跨语言,而RestFul效率太低。

多用于服务器集群间的通信,因此常使用更加高效短小精悍的传输模式以提高效率。

先看看B站马士兵的这个视频来了解RPC的演练过程,以及底层的原理:https://www.bilibili.com/video/BV1zE41147Zq?from=search&seid=13740626242455157002

RMI篇(1)

RMI全称是Remote Method Invocation,远程方法调用。从这个名字就可以看出,他的目标和RPC(Remote Procedure Call 远程过程调用)其实是类似的,是让某个Java虚拟机上的对象调用另一个Java虚拟机中对象上的方法,只不过RMI是Java独有的一种机制。

此处引用文章JAVA RMI 原理和使用浅析的一张图演示RMI的交互过程:

IqT9hR.png

通俗来讲,便是RMI Registry作为client和server的中间人,假设服务端是商品仓库,客户端是购买者,则RMI Registry就是中介,只负责告诉客户可以售卖的商品相关信息。仓库在中介处登记可售商品,客户从中介处查看可购买的商品。客户从中介处获得商品的相关信息,然后交给跑腿(存根stub)去跟仓库人员(骨架skeleton)取货,双方商品信息确认一致,进行交易,客户获得商品。

下面,我们直接从一个例子开始演示RMI的流程。

首先编写一个RMI Server:

/*
 * 在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象,
 * 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上
 * 调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”
 * (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。
 */
public class RMIServer {
    /* extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常,
     * 则表明该方法可被客户端远程访问调用。
     */
    public interface IRemoteHelloWorld extends Remote {
        public String hello() throws RemoteException;
    }

    //java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton
    public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {

        //这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
        protected RemoteHelloWorld() throws RemoteException {
            super();
        }

        @Override
        public String hello() throws RemoteException {
            System.out.println("call from");

            return "Hello World!";
        }
    }

    /* 注册远程对象,向客户端提供远程对象服务
     * 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称
     * 但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求
     * 到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了
     */
    private void start() throws Exception {
        /* 生成stub和skeleton,并返回stub代理引用 */
        RemoteHelloWorld h = new RemoteHelloWorld();
        /* 本地创建并启动RMI Service,被创建的Registry服务将在指定的端口上侦听到来的请求
         * 实际上,RMI Service本身也是一个RMI应用,我们也可以从远端获取Registry:
         *     public interface Registry extends Remote;
         *     public static Registry getRegistry(String host, int port) throws RemoteException;
         */
        LocateRegistry.createRegistry(1099);
        /* 将stub代理绑定到Registry服务的URL上 */
        Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
    }

    public static void main(String[] args) throws Exception {
        new RMIServer().start();
    }
}

一个RMI Server分为三部分:

1.编写远程服务接口,该接口必须继承java.rmi.Remote接口,方法必须抛出java.rmi.RemoteException异常,其中定义我们要远程调用的函数,比如这里的hello()

2.编写远程接口实现类,该实现类必须继承java.rmi.server.UnicastRemoteObject类。

3.一个主类,用来创建Registry,并将上面的类实例化后绑定到一个地址。这就是我们所谓的Server了。

这里是简略的写法,将三个部分放在了一个类里。

接着我们编写一个RMI Client:

public class RMIClient {
    public static void main(String[] args) throws Exception {
        /* 从RMI Registry中请求stub
         * 如果RMI Service就在本地机器上,URL就是:rmi://localhost:1099/hello
         * 否则,URL就是:rmi://RMIService_IP:1099/hello
         */
        RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://192.168.78.128/Hello");
        /* 通过stub调用远程接口实现 */
        String ret = hello.hello();
        System.out.println(ret);
    }
}

客户端就简单多了,使用Naming.lookup在Registry中寻找到名字是Hello的对象,后面的使用就和在本地使用一样了。

虽说执行远程方法的时候代码是在远程服务器上执行的,但实际上我们还是需要知道有哪些方法,这时候接口的重要性就体现了,这也是为什么我们前面要继承Remote并将我们需要调用的方法写在接口IRemoteHelloWorld里,因为客户端也需要用到这个接口。

为了理解RMI的通信过程,我们用wireshark抓包看看:

第一次建立TCP连接是连接远端192.168.78.128的1099端口,这也是我们在代码里看到的端口,二者进行沟通后,我们向

https://blog.csdn.net/xinghun_4/article/details/45787549

https://www.codetd.com/article/10038981