不了解RPC?那我们就亲手实现一个吧

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

0. 什么是 RPC 框架

RPC(Remote Procedure Call) 是一种进程间的通信方式。允许像调用本地服务一样调用远程服务。

简单点说,它是一种通信方式,它的功能就是让你像调用本地服务(函数、方法)一样,调用远程服务(函数,方法)。

比如说,我在服务端有一个接口 (MyService.sayHello())。传统的调用方式是,我们暴露一个 Controller 并绑定到对应的 url地址上,然后通过 http 请求,将参数发送给远程服务器,服务器执行结束后,将结果响应给客户端。

而 RPC 调用方式是,我在客户端导入 MyService 的接口,直接用 MyService.sayHello() 去调用。注意:客户端并没有直接的创建该接口的具体实现对象。而是通过 RPC 的通信方式去来与服务端交互。

1. RPC 框架的基本原理

RPC 框架的基本原理是通过 Socket 和对象序列化来实现的。

首先,客户端和服务端通过 Socket 来建立通信,客户端将需要调用的接口 序列化后发送给服务端。

服务端收到数据后将接口反序列化,通过反射的方式执行该接口。然后将执行结果序列化后发送给客户端。

客户端收到数据后,将结果反序列化,得到接口的执行结果。

下面实现了一个最简单、最基础的 RPC 框架

2. 定义服务

我们先定义一个服务端的 Service,

public interface MyService {
    String sayHello(String name);
}

实现类:

public class MyServiceImpl implements MyService {
    @Override
    public String sayHello(String name) {
        return "hello," + name;
    }
}
复制代码

这个接口就是我们服务端提供的服务。

3. RPC 框架的服务端实现

先在服务端实现 Socket 持续监听客户端发来的数据,收到数据后让 ProducerAgent 去处理。

public class RpcProducer {
    private static Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public static void produce(String host, int port) throws Exception {
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(host, port));
        try {
            while (true) {
                executor.execute(new ProducerAgent(serverSocket.accept()));
            }
        } finally {
            serverSocket.close();
        }
    }
}
复制代码

ProducerAgent 的实现:

public class ProducerAgent implements Runnable {
    Socket client = null;

    public ProducerAgent(Socket accept) {
        client = accept;
    }

    @Override
    public void run() {
        ObjectInputStream inputStream = null;
        ObjectOutputStream outputStream = null;
        try {

            inputStream = new ObjectInputStream(client.getInputStream());

            String interfaceName = inputStream.readUTF();
            Class<?> service = Class.forName(interfaceName);
            String methodName = inputStream.readUTF();
            Class<?>[] paramTypes = (Class<?>[]) inputStream.readObject();
            Object[] args = (Object[]) inputStream.readObject();

            Method method = service.getMethod(methodName, paramTypes);
            Object result = method.invoke(service.newInstance(), args);
            outputStream = new ObjectOutputStream(client.getOutputStream());
            outputStream.writeObject(result);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //省略部分代码
        }
    }
}

复制代码

我们依次读取接口的名称、方法名称、参数类型、参数。然后通过反射的机制执行方法,最后将结果序列化后写入到客户端。

4. RPC 框架的客户端实现

public class LocalAgent<T> {
    public T importer(final Class<?> serviceClass, final InetSocketAddress addr) {
        return (T) Proxy.newProxyInstance(serviceClass.getClassLoader(),
                new Class<?>[]{serviceClass.getInterfaces()[0]},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        ObjectOutputStream outputStream = null;
                        ObjectInputStream inputStream = null;
                        Socket socket = null;
                        try {
                            socket = new Socket();
                            socket.connect(addr);
                            outputStream = new ObjectOutputStream(socket.getOutputStream());

                            outputStream.writeUTF(serviceClass.getName());
                            outputStream.writeUTF(method.getName());
                            outputStream.writeObject(method.getParameterTypes());
                            outputStream.writeObject(args);

                            inputStream = new ObjectInputStream(socket.getInputStream());
                            return inputStream.readObject();
                        } finally {
                            //省略部分代码
                        }
                    }
                });
    }
}

复制代码

客户端做的事情是与服务端建立 Socket 通信,然后依次写入 接口名、方法名、参数类型、参数。注意:写入顺序一定要与服务端的读取顺序一致

然后接收服务端的执行结果,反序列化为实际类型。

好了,下篇文章说明下怎么使用我们创建的这个RPC框架。