Deep dive into javaRMI

 Foreword: The use of JavaRMI is not troublesome, what really interests me is stubs and skeletons, and what interests me more is how the RMI framework makes clients feel like they are calling local methods to use remote object methods. (It is recommended to understand the basic usage of rmi before reading this article, please refer to the attached demo)

This article focuses on the following issues:

  • What are stubs and skeletons objects? Who created them? What role?
  • How does the client find the server? How to know the port where the server publishes the service?
  • Is a registry required? What does it do?

1. Return to essential needs

        First of all, forget the registration center. The initial usage scenario is as follows: the server server publishes the service, the client client needs to use the service published by the server (which can be understood as api), and the server inherits java.rmi.server.UnicastRemoteObject. The problem is that Server and Client run on different machines, but Client wants to execute a method published by Server on a remote machine.

        It is easy to think that Rmi's attempt to solve the above problems will definitely involve Socket network programming, because the Server runs on a remote machine, and there are two key points involved in the specific implementation:

  • How can the client client be decoupled from handling network connections so that it can focus on business?
  • How does the client Client call a method on a remote machine as if it were a local method?

        The above problem is the meaning of the existence of stubs and skeletons. It is the existence of stubs and skeletons that allows clients and servers to not need to deal with network-related methods. The stubs also implement the same java.rmi.server.UnicastRemoteObject interface as the server, so that when the client wants to call a method on the server, it can call the same method on the stubs, but only the processing logic related to the network is on the stub. There is no corresponding business processing logic. For example, if an add method is published on the server, there is also an add method in the stub, but the add method on the stub does not contain the implementation of the business logic part, it only contains the details of how to connect to the remote skeleton and call the method information, parameters, return values, etc. The entire implementation architecture is shown in the following figure:

 

         In short, the client Client talks to the stub, the stub talks to the skeleton, and the skeleton talks to the Server. The Server executes the real method and returns the result after the meal. It can also be seen from the figure that the function of Rmi is mainly composed of four parts: Client, stub, skeleton, and Server.

2. Socket layer details

The whole process of socket layer communication:

1.Server publishes a service on a remote machine and listens on a port. This port is a port randomly selected by the JVM or OS at runtime. It can be said that the Server publishes services on the port of the remote machine.

2. Client calls the method published on Server. In fact, the Client does not know where the Server is, nor which port the Server is listening on, but the Client has a stub, and the stub knows all these things, so that the Client can call any method on the stub that he wants to call;

3. The stub connects to the port monitored by the Server and sends parameters. The process is as follows:

       > Client connects to the port that Server listens on;

       > Server receives the request and creates a socket to handle the connection;

       > Server continues to monitor incoming requests;

       > Client and Server use a double-send negotiated protocol to transmit parameters and results; (the protocol can be JRMP or iiop)

4. The method is executed on the remote server and the execution result is returned to the stub

5. The stub returns the result to the Client, as if the stub executed this method.

 

However, after carefully pondering the above process, it seems that there are still loopholes. The second point is that the stub knows where the server is, and the stub knows the port that the server is listening on, but the port for the server to publish the service is random. How does the stub know the server? It is impossible to create a stub that knows everything without knowing the IP and Port of the Server. This also leads to the existence value of the RMIRegistry registry.

3. The role of the RMIRegistry registry

         RmiRegistry can be regarded as a service, it maintains a hashMap, the key is publicName, and the value is stunObject. For example, a service Calculator is arranged on a remote machine and published on a random port (the client cannot know this port). After the server publishes the service, it will create a stub object on the server side at the same time, and then register it with RmiRegistry. The central RmiRegistry knows all the conditions for network transmission between the Server and the Client. Currently, the premise is that the Client can contact RmiRegistry. Fortunately, the default port of the RMIRegistry publishing service is public and well-known, namely 1099. Of course, you can also specify the port of the registration service yourself, so that the Client can get the stub object from the registry center RMIRegistry.

4. Explain the whole process with examples

4.1 Server Publishing Service

    Define the remote service interface:

 

/**
 * Define a remote interface, which must inherit the Remote interface, and the methods that need to be called remotely must throw RemoteException
 * @author Administrator
 */
public interface IHello extends Remote {
    /**
     * simply returns the words "Hello World!"
     * @return returns the words "Hello World!"
     * @throws java.rmi.RemoteException
     */
    public String helloWorld() throws RemoteException;

    /**
     * A simple business method that returns the corresponding greeting based on the incoming person's name
     * @param someBodyName  人名
     * @return returns the corresponding greeting
     * @throws java.rmi.RemoteException
     */
    public String sayHelloToSomeBody(String someBodyName) throws RemoteException;
}

    Server server implementation:

 

/**
 * Implementation of remote interface
 * @author Administrator
 */
public class HelloImpl extends UnicastRemoteObject implements IHello {
    private static final long serialVersionUID = -5638936712154214504L;

    /**
     * Because the constructor of UnicastRemoteObject throws RemoteException, the default constructor here must be written, and it must be declared to throw RemoteException
     * @throws RemoteException
     */
    public HelloImpl() throws RemoteException {}
    
    @Override
    public String helloWorld() throws RemoteException {
        return "Hello World!";
    }

    @Override
    public String sayHelloToSomeBody(String someBodyName) throws RemoteException {
        return "你好," + someBodyName + "!";
    }

}

    The server publishes the service, and the general steps are as follows:

  1. Create a remote service object, and implement;
  2. Publish the RmiRegistry service on the specified port, and the user client connects;
  3. publish services on the registry;
public static void main(String[] args) {
        try {
            // 1. Create a remote object
            IHello rhello = new HelloImpl();
            // 2. An instance of the remote object registry Registry on the local host, and specify the port as 8888. This step is essential. If the registry is not created, the object cannot be bound to the remote registry.
            LocateRegistry.createRegistry(8888);

            // 3. Register the remote object with the RMI registration server and name it RHello
            //The standard format of the bound URL is: rmi://host:port/name (the protocol name can be omitted, and the following two writing methods are correct)
            //Naming.bind("rmi://localhost:8888/RHello",rhello);
            Naming.bind("//localhost:8888/RHello",rhello);

            System.out.println(">>>>>INFO: The remote IHello object is bound successfully!");
        } catch (RemoteException e) {
            System.out.println("An exception occurred while creating the remote object!");
            e.printStackTrace ();
        } catch (AlreadyBoundException e) {
            System.out.println("Duplicate binding object exception occurred!");
            e.printStackTrace ();
        } catch (MalformedURLException e) {
            System.out.println("URL malformed exception!");
            e.printStackTrace ();
        }

    The above is the whole process of the server server publishing service.

 4.2 The client uses the service published by the server

    Define a service interface on the client that is the same as the remote interface (the package name should be the same as the interface package name published by the server)

    

/**
 * (The package name should be the same as the interface package name published by the server)
 * @author Administrator
 */
public interface IHello extends Serializable {
    /**
     * simply returns the words "Hello World!"
     * @return returns the words "Hello World!"
     * @throws java.rmi.RemoteException
     */
    public String helloWorld() throws RemoteException;

    /**
     * A simple business method that returns the corresponding greeting based on the incoming person's name
     * @param someBodyName  人名
     * @return returns the corresponding greeting
     * @throws java.rmi.RemoteException
     */
    public String sayHelloToSomeBody(String someBodyName) throws RemoteException;
}
   To use the service published by the server on the client, the steps are as follows:

 

 

  1. Find stun from the registry;
  2. use the service.
/**
 * Client-side test, the client calls the remote method on the remote object and returns the result.
 * @author Administrator
 */
public class HelloClient {

    public static void main(String[] args) {
        try {
            // 1. Find the object named RHello in the RMI service registry and call the method on it
            IHello rhello =(IHello) Naming.lookup("rmi://localhost:8888/RHello");
            // 2. Call the service
            System.out.println(rhello.helloWorld());
            System.out.println(rhello.sayHelloToSomeBody("熔岩"));
        } catch (NotBoundException e) {
            e.printStackTrace ();
        } catch (MalformedURLException e) {
            e.printStackTrace ();
        } catch (RemoteException e) {
            e.printStackTrace ();   
        }
    }

}

 

Supplementary note: I debugged RMI on a multi-NIC ubantu system before, and found that the URI that the server publishes and binds to is not what I specified. If the server has multiple network cards, it only uses any one of the network cards, so if you enable the RMI service on a server with multiple network cards, you must specify the IP used. The usual way is as follows:

  • If it is a CS program, before starting the RMI service, specify the IP address used by the RMI service through the following code:

                    System.setProperty("java.rmi.server.hostname","192.168.1.111");

  • If it is a WEB program, add the following startup parameters:

                    -Djava.rmi.server.hostname=192.168.1.111

    Take Tomcat as an example (Windows)

    If you use startup.bat to start, add the following statement to the catalina.bat file:

    set CATALINA_OPTS=-Djava.rmi.server.hostname=192.168.1.111

 Project Baidu network disk download address: http://pan.baidu.com/s/1eRQE0aY , password: 4mvn

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326196331&siteId=291194637