Java Remote Method Invocation (RMI)

一个 RemoteObject 在 export 后在 RMI Server 端持有一个 UnicastServerRef 对象(真正被 export 的 Proxy 对象持有的是 UnicastRef 对象),在 RMI Registry 端保存的是 Proxy 对象,RMI Client 通过 RMI Registry lookup 获取到 Proxy 对象,在 RMI Client 端对 RemoteObject 的操作,最终都通过 RemoteObjectInvocationHandler 委托给 UnicastRef#public Object invoke(Object proxy, Method method, Object[] args)。

UnicastRef 和 UnicastServerRef

UnicastRef 提供了invoke 方法,UnicastServerRef提供了 exportObject 方法,在 RMI Client 端,Proxy 远程对象持有一个 UnicastRef 对象,一个 UnicastRef 持有一个 LiveRef 对象;在 RMI Server 端,一个 RemoteObject 持有一个 UnicastServerRef 对象, UnicastServerRef 持有一个 LiveRef 对象(一个 remote object reference = LiveRef = Endpoint(host:port)/ObjID)。LiveRef 持有 Endpoint 对象,Endpoint 对象有两个方法

/**
 * Returns the transport for incoming connections to this endpoint.
 **/
Transport getInboundTransport();

/**
 * Returns transport for making connections to remote endpoints.
 **/
Transport getOutboundTransport();

Transport getOutboundTransport() 方法在 RMI Client 调用,获取到 Transport 之后,通过 Trasport#getChannel 获取到 TCPChannel 对象,最后通过 TCPChannel#newConnection() 方法获取到 Connection 对象,对于 TCPConnection 来说,一个 TCPConnection 对象持有一个 Socket 对象,及通过 Socket 获取到的 InputStream 和 OutputStream;

Transport 层的这些对象的构造方法里,只传port的就是给服务器端用的;

TCPTransport 里的 getChannel 是给 RMI Client 用的?(从调用关系上看,此方法最终是通过 UnicastRef#invoke#newCall#free 三个方法调用,都是在 RMI Client 才会调用的;)其他的都是给 RMI Server 用的?

exportObject

那么 exportObject 返回的对象,如何有这些能力的呢,发布对象的时候,我们需要给 UnicastRemoteObject#exportObject 传入远程对象和端口号,
UnicastRemoteObject 会将传入的信息 new 一个 UnicastServerRef2 对象,然后将发布对象的工作委托给 UnicastServerRef2 对象的 exportObject 方法;
在 UnicastServerRef2 中,host+port 用来构建 LiveRef 对象(根据 host + port 构建 endpoint + ObjID -> liveref),然后将发布对象的工作委托给 LiveRef;LiveRef委托给Endpoint,Endpoint委托给 transport,transport里创建 ServerSocket;

为什么拿到了 RMI Server 发布的远程对象的序列化的反序列化对象之后,就能跟 RMI Server 上远程对象通过 Socket 通信了呢?
export的远程对象并不是开发者直接传入的这个对象,

RemoteHello remoteHello = new RemoteHelloImpl();
RemoteHello stub = (RemoteHello) UnicastRemoteObject.exportObject(remoteHello, 4000);

比如上面这段代码,这个 stub 是 remoteHello(RemoteHelloImpl类的实例),通过JDK动态代理之后的对象,那么动态代理给其加了什么功能呢?

分析 UnicastServerRef # exportObject 得知,exportObject 返回的对象,是使用 JDK 动态代理创建的对象,从 RemoteObjectInvocationHandler 可以看出,这个代理对象的能力是,将对这个代理对象的方法调用,通过 RemoteObject 持有的 UnicastRef 对象的 invoke 方法启动本地 Socket 与远程对象的交互,从而得到远程对象的执行结果。怎么实现这个目标的?

构建代理对象时,先构建一个 UnicastRef 对象(客户端的远程对象引用),这时候注意:
这是 UnicastServerRef 的方法

protected RemoteRef getClientRef() {
    return new UnicastRef(ref);
}

这是 UnicastServerRef2 的方法

protected RemoteRef getClientRef() {
    return new UnicastRef2(ref);
}

他们在构建 UnicastRef 对象时使用了对应的 UnicastServerRef 对象的 LiveRef 对象(这个对象持有的就是远程的对象的位置信息,endpoint=objid),LiveRef 对象的序列化和返序列化直接调用了其持有的Endpoint 和ObjID的信息,看一下 Endpoint 的信息如下,就是host和port信息;

/**
 * Write endpoint to output stream.
 */
public void write(ObjectOutput out) throws IOException {
    if (csf == null) {
        out.writeByte(FORMAT_HOST_PORT);
        out.writeUTF(host);
        out.writeInt(port);
    } else {
        out.writeByte(FORMAT_HOST_PORT_FACTORY);
        out.writeUTF(host);
        out.writeInt(port);
        out.writeObject(csf);
    }
}

/**
 * Get the endpoint from the input stream.
 * @param in the input stream
 * @exception IOException If id could not be read (due to stream failure)
 */
public static TCPEndpoint read(ObjectInput in)
    throws IOException, ClassNotFoundException
{
    String host;
    int port;
    RMIClientSocketFactory csf = null;

    byte format = in.readByte();
    switch (format) {
      case FORMAT_HOST_PORT:
        host = in.readUTF();
        port = in.readInt();
        break;

      case FORMAT_HOST_PORT_FACTORY:
        host = in.readUTF();
        port = in.readInt();
        csf = (RMIClientSocketFactory) in.readObject();
        if (csf != null && Proxy.isProxyClass(csf.getClass())) {
            throw new IOException("Invalid SocketFactory");
        }
      break;

      default:
        throw new IOException("invalid endpoint format");
    }
    return new TCPEndpoint(host, port, csf, null);
}

到这里,代理对象,通过持有带有远程对象位置信息(LiveRef)的 UnicastRef 对象,已经可以在未来被某个 RMI Client 获取到的时候,可以反过来 export 此对象的 RMI Server 上的此对象发起 Socket 连接了。RemoteObjectInvocationHandler 将 method.invoke 委托给 UnicastRef#invoke 方法发起远程通信;走时序图里的流程。

试一下直接使用 UnicastServerRef2 是否可以将多个对象发布到一个端口?

Registry

Registry 接口的实现为 RegistryImpl,其通过继承 java.rmi.server.RemoteServer ,间接实现了 java.rmi.Remote 接口,所以 RegistryImpl 是一个远程对象,sun.rmi.registry.RegistryImpl_Stub 和 sun.rmi.registry.RegistryImpl_Skel 为使用 RMIC 工具生成的存根和骨架类。
Registry 一共由5个方法, bind ,unbind和rebind是 RMI Server 用来注册和解绑远程对象的;list 和 lookup 是 RMI Client 用来查找远程对象的。

registry bind & lookup

RMI Server 往 RMI Registry 注册远程对象:

此段的主要逻辑在 RegistryImpl_Stub#void bind(java.lang.String $param_String_1, java.rmi.Remote $param_Remote_2) 方法中,

out.writeObject(name);
out.writeObject(Remote);

RMI Registry 接收到调用之后的逻辑在 RegistryImpl_Skel@dispatch(java.rmi.Remote obj, java.rmi.server.RemoteCall remoteCall, int opnum, long hash) 方法中,

name = SharedSecrets.getJavaObjectInputStreamReadString().readString(in);
Remote = (java.rmi.Remote) in.readObject();

其从流中读出 name 和 Remote 对象;然后调用 RegistryImpl#bind,将 name->Remote 放到其持有的全局静态变量中:

private Hashtable<String, Remote> bindings
        = new Hashtable<>(101);

会写 TransportConstants.NormalReturn ,调用结束(这个逻辑在,StreamRemoteCall#getResultStream 方法中)

RMI Client 从 RMI Registry 寻找远程对象:

name = SharedSecrets.getJavaObjectInputStreamReadString().readString(in);
java.rmi.Remote remote = RegistryImpl.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);
}

这三个对象创建的时候是这么个关系;

ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
                     endpoint.getClientSocketFactory(),
                     endpoint.getServerSocketFactory());
ch = new TCPChannel(TCPTransport.this, ep);
conn = new TCPConnection(ch, socket, bufIn, bufOut);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈振阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值