6

RMI之由浅入深(一)

 3 years ago
source link: http://www.cnblogs.com/0x7e/p/14254746.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0x01、什么是RMI

RMI(Remote Method Invocation)即Java远程方法调用,RMI用于构建分布式应用程序,RMI实现了Java程序之间跨JVM的远程通信。顾名思义,远程方法调用:客户端比如说是在手机,然后服务端是在电脑;同时都有java环境,然后我要在手机端调用服务端那边的某个方法,这就是,远程方法调用;

使用RMI的时候,客户端对远程方法的调用就跟对同一个Java虚拟机(也就是本地)上的方法调用是一样的。一般调用和RMI调用有一点不同,虽然对客户端来说看起来像是本地的,但是客户端的stub会通过网络发出调用,所以会抛出异常;其中还是会涉及到Socket和串流的问题,一开始是本地调用,然后就代理(stub)会转成远程,中间的信息是如何从Java虚拟机发送到另外一台Java虚拟机要看客户端和服务端的辅助设施对象所用的协议而定; 使用RMI的时候,需要选择协议:JRMP或IIOP协议;JRMP是RMI的原生的协议,也就是默认JRMP协议。而IIOP是为了CORBA而产生的 ~~~

远程方法调用,具体怎么实现呢?远程服务器提供具体的类和方法,本地会通过 某种方式 获得远程类的一个代理,然后通过这个代理调用远程对象的方法,方法的参数是通过序列化与反序列化的方式传递的,所以,

1.只要服务端的对象提供了一个方法,这个方法接收的是一个Object类型的参数

2.且远程服务器的classpath中存在可利用pop链,那么我们就可以通过在客户端调用这个方法,并传递一个精心构造的对象的方式来攻击rmi服务。

某种方式获得远程对象的代理,那么具体是怎么的实现机制呢?RMI模式中除了有Client与Server,还借助了一个Registry(注册中心)。

Server Registry Client 提供具体的远程对象 一个注册表,存放着远程对象的位置(ip、端口、标识符) 远程对象的使用者

其中Server与Registry可以在同一服务器上实现,也可以布置在不同服务器上,现在一个完整的RMI流程可以大概描述为:

  1. Registry先启动,并监听一个端口,一般为1099
  2. Server向Registry注册远程对象
  3. Client从Registry获得远程对象的代理(这个代理知道远程对象的在网络中的具体位置:ip、端口、标识符),然后Client通过这个代理调用远程方法,Server也是有一个代理的,Server端的代理会收到Client端的调用的方法、参数等,然后代理执行对应方法,并将结果通过网络返回给Client。

两图胜千言:

fYvmArJ.png!mobile

nAJjmiz.png!mobile

其实跟上图都类似

0x02、RMI的框架与解析

zmI7fya.png!mobile

RMI调用远程方法的大致如下:

  1. RMI客户端 在调用远程方法时会先创建 Stub(sun.rmi.registry.RegistryImpl_Stub)
  2. Stub 会将 Remote 对象传递给 远程引用层(java.rmi.server.RemoteRef) 并创建 java.rmi.server.RemoteCall(远程调用) 对象。
  3. RemoteCall 序列化 RMI服务名称Remote 对象。
  4. RMI客户端远程引用层 传输 RemoteCall 序列化后的请求信息通过 Socket 连接的方式传输到 RMI服务端远程引用层
  5. RMI服务端远程引用层(sun.rmi.server.UnicastServerRef) 收到请求会请求传递给 Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)
  6. Skeleton 调用 RemoteCall 反序列化 RMI客户端 传过来的序列化。
  7. Skeleton 处理客户端请求: bindlistlookuprebindunbind ,如果是 lookup 则查找 RMI服务名 绑定的接口对象,序列化该对象并通过 RemoteCall 传输到客户端。
  8. RMI客户端 反序列化服务端结果,获取远程对象的引用。

而更通俗点来说:

  1. 客户端请求代理
  2. Stub编码处理消息
  3. 消息传输
  4. 到达管家skeleton并处理信息
  5. 管家skeleton把信息提交给server
  6. server接收到请求
  7. server把请求的结果给管家
  8. 管家skeleton把结果转交给stub
  9. 代理Stub对结果解码
  10. Stub把解码的结果交给client。
    更容易的通俗易懂~~~

1、⾸先客户端连接Registry,并在其中寻找Name是Hello的对象

nyuUjeq.png!mobile

2、这个对应数据流中的Call消息;

eeeQJ3r.png!mobile

3、然后Registry返回⼀个序列化的数据,这个就是找到的Name=Hello的对象,这个对应数据流中的ReturnData消息;

fiURZjZ.png!mobile

4、客户端反序列化该对象,发现该对象是⼀个远程对象,地址在 169.254.20.76:39098 ,这边可能会有疑问,这里面没有,怎么会知道端口号啥的

IFBVf2b.png!mobile

可以看出,其实端口是跟在远程地址的后面,只不过是16进制的,需要切换一下

ZRbeYb6.png!mobile

5、于是再与这个地址建⽴TCP连接;在这个新的连接中,才执⾏真正远程⽅法调⽤,也就是 hello()

6NbeeeF.png!mobile

0x03、rmi测试代码

RMIDemo.java

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface rmiDemo extends Remote {
    public String hello() throws RemoteException;
}

RMIDemoImpl.java

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RMIDemoImpl extends UnicastRemoteObject implements rmiDemo{


    protected RMIDemoImpl() throws RemoteException {
        System.out.println("构造方法");
    }

    public String hello() throws RemoteException {
        System.out.println("hello方法被调用");
        return "hello,world";
    }
}

RMIServer.java

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

public class RMIServer {
    public static void main(String[] args) throws RemoteException {
        rmiDemo hello = new RemoteHelloWorld();//创建远程对象
        Registry registry = LocateRegistry.createRegistry(1099);//创建注册表
        registry.rebind("hello",hello);//将远程对象注册到注册表里面,并且设置值为hello

    }
}

RMIClient.java

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

public class RMIServer {
    public static void main(String[] args) throws RemoteException {
        rmiDemo hello = new RemoteHelloWorld();//创建远程对象
        Registry registry = LocateRegistry.createRegistry(1099);//创建注册表
        registry.rebind("hello",hello);//将远程对象注册到注册表里面,并且设置值为hello

    }
}

网上参考学习,如若有错,请各位大佬指出

zIza22R.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK