RMI(Remote Method Invocation)远程方法调用是 java 的核心技术之一。是 Enterprise JavaBeans 的基础技术,是 java 建立分布式应用程序的强大支柱。

RMI 允许一个应用程序访问另外一个服务器或虚拟机上的对象,方法和服务,它使远程方法调用就像在本地调用一样简单。它为用户屏蔽了底层的网络传输细节,使用的时候只需适当处理异常即可。所以 RMI 是非常容易使用的,但同时是非常强大的。

  • RMI 协议的数据序列化目前支持以下两种模式:

    1. 基于 JDK 本身的对象序列化
    2. 基于 HTTP 协议的数据序列化

    协议数据序列化原文说明如下:

    The RMI protocol makes use of two other protocols for its on-the-wire format: Java Object Serialization and HTTP. The Object Serialization protocol is used to marshal call and return data. The HTTP protocol is used to "POST" a remote method invocation and obtain return data when circumstances warrant. Each protocol is documented as a separate grammar. Nonterminal symbols in production rules may refer to rules governed by another protocol (either Object Serialization or HTTP). When a protocol boundary is crossed, subsequent productions use that embedded protocol.
  • 数据传输数据包一共分两种,一种是数据输出流,另一种是数据输入流。

    输出流与输入流都是成对出现和使用。

    输出流数据包格式

    输出流数据包的内容说明如下:

    • Header 头
    • Messages 消息体
    • HttpMessage(可选)

    Header:头数据包说明

    0x4a 0x52 0x4d 0x49 Version Protocol

    Header 包含四个固定字节的标识加版本号,协议信息构成

    Version 数据包说明(两个字节)

    0x00 0x01

    Protocol 协议(一个字节),一共分三种 StreamProtocolSingleOpProtocol 和 MultiplexProtocol

    分别对应的值如下:

    • StreamProtocol0x4b
    • SingleOpProtocol0x4c
    • MultiplexProtocol0x4d

    Messages:数据包说明

    针对 Message 的消息体,由与上面的 Protocal 协议类型指定相关。

    如果是 SingleOpProtocol 则消息体只包含一条消息。一般用于 HTTP 方式请求。

    The Messages are wrapped within a particular protocol as specified by Protocol. For the SingleOpProtocol, there may only be one Message after the Header, and there is no additional data that the Message is wrapped in. The SingleOpProtocol is used for invocation embedded in HTTP requests, where interaction beyond a single request and response is not possible.

    SingleOpProtocol 和 MultiplexProtocol 消息则需要服务器返回 0x4e 字节作为响应。

    Message 的类型包含以下三种:

    1. Call 表示 RMI 的调用操作
    2. Ping 是检测服务是否运行正常
    3. DgcAck 当客户端收到服务端的对象消息后告诉服务器可以把返回值对象进行 gc 操作。

    Call 消息数据内容:

    0x50 CallData

    Ping 消息数据内容:

    0x52

    DgcAck 消息数据内容:

    0x54 UniqueIdentifier

    CallDataRMI 方法请求数据包

    ObjectIdentifier Operation Hash Arguments(可选)
    • ObjectIdentifier (the target of the call)

    • Operation (a number representing the method to be invoked)

    • Hash (a number that verifies that client stub and remote object skeleton use the same stub protocol)

    • Arguments(a list of zero or more Arguments for the call)

    UniqueIdentifier 数据包内容(一个字节)

    Number Time Count

    ArgumentsValues 对象

    Object

    HttpMessage:数据包说明

    HttpPostHeader Header Message

    HttpPosteHeaderHTTP 标准的请求 Header

    Header 头和 Message 数据包与上相同。

    输入流数据包格式

    输入流数据包的内容有以下三个方式:

    ReturnData is the result of a "normal" RMI call

    An HttpReturn is a return result from an invocation embedded in the HTTP protocol

    A PingAck is the acknowledgment for a Ping message

    ReturnData 数据包:

    0x4e Returns

    Returns 数据包,包含 ReturnData 和 PingAck

    ReturnData 数据包:

    0x51 ReturnValue

    PingAck 数据包:

    0x53

    ProtocolNotSupported 数据包:

    0x4f

    ReturnValue:返值数据包结构

    0x01 UniqueIdentifier 返回值对象(可选)
    0x02 UniqueIdentifierException 对象
    Note - ObjectIdentifier, UniqueIdentifier, and EndpointIdentifier are not written out using default serialization, but each uses its own special write method (this is not the writeObject method used by object serialization); the write method for each type of identifier adds its component data consecutively to the output stream.
  • Count

    short

    Exception

    java.lang.Exception

    Hash

    long

    Hostname

    UTF

    Number

    int

    Object

    java.lang.Object

    ObjectNumber

    long

    Operation

    int

    PortNumber

    int

    Primitive

    byte, int, short, long...

    Time

    long

  • 在使用 RMI 发布服务时,会使用到两个端口。

    一个是 RegisterPort,这个是 RMI 的服务注册端口,通过以下 API 来指定。而且服务注册端口必须要指定,默认使用 1099 端口。

    Registry reg = LocateRegistry.getRegistry(registryPort);

    注册端口是客户端服务连接的端口。

    一个是 ServicePort,这个是 RMI 的服务的数据传输端口。该端口是真正在 RMI 客户端与服务端进行数据通信交互的端口。是由注册端口发现有客户端连接后,进行后续分配的端口。默认值为 0 表示使用匿名随机端口。

    API 的指定方式如下:

    UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort);