远程调用

首先,定义一下本文中的几个概念:

远程调用:跨进程的方法调用
客户进程:发起方法调用的进程
服务进程:实际实现方法的进程

如果需要设计一个“远程调用”的机制,我们需要考虑以下几个问题:

  1. 客户进程如何发起方法调用
  2. 服务进程如何知道要调用的方法名,如何接收方法参数
  3. 客户进程与服务进程如何通信

基础模式如下:

1

HTTP

如果按照这里的定义,HTTP 访问本质上也是一个远程调用。针对 HTTP 协议规范,我们看看它的实现原理:

  1. 用户在浏览器地址栏输入“http://www.chelaile.net.cn/index.html”,然后打开一个网页

  2. 浏览器内部发起http请求

即:

GET /index.html HTTP/1.1
Host: www.chelaile.net.cn

代码表示如下:

browser.open("http://www.chelaile.net.cn/index.html");

browser.parse("http://www.chelaile.net.cn/index.html");

data = browser.marshalling({
    Host : www.chelaile.net.cn,
    method : get,
    page : /index.html
});

browser.send(data)
  1. 浏览器通过 TCP 协议将 data 发往服务器

  2. 服务器接收 data

即:

server.receive(data);

server.unmarshalling(data); //得到{
                                        Host : www.chelaile.net.cn,
                                        method : get,
                                        page : /index.html
                                    }

server.get("/index.html");
    

所以,远程调用的核心过程就是:

  1. 客户进程与服务进程协商编组格式,即“协议”
  2. 客户进程将“调用”本身所需的所有数据按照“协议”进行编组
  3. 客户进程将编组后的数据发送给服务进程
  4. 服务进程收到数据之后按照“协议”进行反编组
  5. 服务进程根据反编组后的数据进行相应的方法调用

通常来说,第3步属于底层通信细节,是“协议”独立的。所以,使用Socket即可。图形表示如下:

2

RPC

RPC 的 5 个部分:

  1. Client
  2. Client-stub
  3. RPCRuntime
  4. Server-stub
  5. Server

对应表示即:

3

RMI

RPC 更接近于“远程调用”协议,而 RMI 就是特定的 java “远程调用”实现了。由于 java 不能进行动态类或者方法定义,所以一切调用都是基于接口来进行。

一个简单的例子如下:

  1. 约定服务接口

        public interface IHelloService extends Remote {
            public String sayHello() throws RemoteException;
        }
  1. 实现服务

        public class HelloServiceImpl extends UnicastRemoteObject implements IHelloService {
            protected HelloServiceImpl() throws RemoteException {
            }
        
            protected HelloServiceImpl(int port) throws RemoteException {
                super(port);
            }
        
            protected HelloServiceImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException {
                super(port, csf, ssf);
            }
        
            @Override
            public String sayHello() throws RemoteException {
                return "this is ServiceImpl";
            }
        }
  1. 注册服务

        public class HelloServer {
            public static void main(String[] args) {
                try {
                    IHelloService service = new HelloServiceImpl();
                    LocateRegistry.createRegistry(20000);
                    Naming.bind("rmi://localhost:20000/Hello", service);
                } catch (AlreadyBoundException | MalformedURLException | RemoteException e) {
                    e.printStackTrace();
                }
                System.out.println("bind success");
            }
        }
  1. 客户端查找服务并调用

        public class HelloClient {
            public static void main(String[] args) {
                try {
                    IHelloService helloService = (IHelloService) Naming.lookup("rmi://localhost:20000/Hello");
                    String resp = helloService.sayHello();
                    System.out.println(resp);
                } catch (NotBoundException | MalformedURLException | RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        //output 
        this is ServiceImpl

上面的代码中并没有具体的“编组”或者 stub 相关的代码,因为 RMI 处理了这些细节,从而在更高的层面来对外提供接口。