动手实现一个简单的RPC

本文介绍了如何动手实现一个简单的RPC框架,包括创建`rpc-common`、`rpc-server`和`rpc-client`三个Maven项目。在`rpc-common`中定义了`Student`对象和服务接口`StudentService`。`rpc-server`实现了服务代理`RpcServerProxy`,使用BIO处理请求,而`rpc-client`通过代理类`RpcClientProxy`发送请求。整个过程利用Java的序列化和动态代理技术完成,提供了对RPC基本原理的理解。

RPC

remote procedure call,远程过程调用,顾名思义就是向远程计算机发送请求来获取服务。对于开发人员来说,调用远程服务就像是调用本地服务一样便捷。尤其是在微服务盛行的今天,了解RPC的原理过程是十分有必要的。今天我就来实现一个简单的RPC的功能。
建立三个maven项目

  • rpc-sever: 服务端,即服务的提供者
  • rpc-client: 客户端,即服务的消费者
  • rpc-common: 公共包,提供服务端和客户端依赖的公共包
先编写rpc-common
  • 定义一个简单的对象Student
@Data
public class Student implements Serializable {
    private String stuName;
    private Integer stuAge;
}

上面用到的lombok的注解,可以节省些代码,在maven中引入lombok依赖并在IDE里安装好lombok插件即可。

  • 定义一个服务的接口StudentService
public interface StudentService {
    String sayHello(String student);

    String printStudent(Student student);
}
  • 定义一个请求的RpcRequest,来封装请求的服务,以及参数
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RpcRequest implements Serializable {
    private String className;
    private String methodName;
    private Object[] parameters;
}

这些就是rpc-client和rpc-sever依赖的一些公共类,在rpc-client和rpc-sever的pom文件都依赖进去。

再编写rpc-sever

编写服务实现类StudentServiceImpl

public class StudentServiceImpl implements StudentService {
    @Override
    public String sayHello(String student) {
        return "hello," + student;
    }

    @Override
    public String printStudent(Student student) {
        return student.toString();
    }
}

编写服务的代理类RpcServerProxy,这里用BIO方式,来处理请求。

public class RpcServerProxy {
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    public void publisher(Object server, int port) {
        try (ServerSocket serverSocket = new ServerSocket(port);){
            while (true) {
                //持续监听请求
                Socket socket = serverSocket.accept();
                executorService.execute(new ProcessHandler(socket, server));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里简单说明一下代码,就是定义一个固定容量的线程池,和一个服务发布的方法,当服务监听对应端口是否有请求进来,如果有,分发一个线程去处理对应的请求,处理者就是ProcessHandler
编写ProcessHandler

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProcessHandler implements Runnable {
    private Socket socket;
    private Object service;

    @Override
    public void run() {
        System.out.println("开始处理请求-----------");
        try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
            RpcRequest request = (RpcRequest) inputStream.readObject();
            System.out.println("客户端请求:" + request);
            Object result = invoke(request);
            try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())){
                outputStream.writeObject(result);
                outputStream.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

    private Object invoke(RpcRequest request) {
        Object[] args = request.getParameters();
        Class<?>[] types = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            types[i] =args[i].getClass();
        }
        try {
            Method method = service.getClass().getMethod(request.getMethodName(),types);
            Object result = method.invoke(service, args);
            return result;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

这个类的作用就是来处理我们服务端收到的每个请求,线程执行run方法,先反序列化请求,即我们封装的RpcRequest,接着调用invoke()方法,通过反射来调用对应的服务的实现方法,将返回结果序列化并冲刷出去并关闭流。
这样一个rpc-sever就写好了,来写一个Main主类,开启我们的服务。

public class Main {
    public static void main(String[] args) {
        StudentService studentService = new StudentServiceImpl();
        RpcServerProxy rpcServerProxy = new RpcServerProxy();
        rpcServerProxy.publisher(studentService, 8080);
    }
}

主类对外暴露8080端口,提供studentService服务。

最后编写rpc-client

我们也采用代理的方式,编写一个客户端代理类RpcClientProxy

public class RpcClientProxy {

    public <T> T clientProxy(Class<T> interfaceCls, String host, Integer port) {
        return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(),
                        new Class<?>[] {interfaceCls}, new ClientInvocationHandler(host, port));
    }
}

编写我们的ClientInvocationHandler

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientInvocationHandler implements InvocationHandler {
    private String host;
    private Integer port;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcRequest request = new RpcRequest();
        request.setMethodName(method.getName());
        request.setParameters(args);
        RpcTransport rpcTransport = new RpcTransport(host, port);
        return rpcTransport.sendRequest(request);
    }
}

这里就主要是封装参数为一个RpcRequest,通过rpcTransport.sendRequest()方法,来将我们的请求发送出去。

@Data
@AllArgsConstructor
public class RpcTransport {
    private String host;
    private Integer port;

    public Object sendRequest(RpcRequest request) {
        try (Socket socket = new Socket(host, port)) {
            //建立连接
            System.out.println("建立一个连接-------");
            try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {
                outputStream.writeObject(request);
                outputStream.flush();
                try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
                    Object result = inputStream.readObject();
                    return result;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

RpcTransport完成的功能很简单,就是把我们的request发送出去,并读取返回并反序列为我们想要的结果。
这样rpc-client也编写完了,接下来编写客户端的Main主类,

public class Main {
    public static void main(String[] args) {
        RpcClientProxy rpcClientProxy = new RpcClientProxy();
        StudentService studentService = rpcClientProxy.clientProxy(StudentService.class, "localhost", 8080);
        String result = studentService.sayHello("小张");
        System.out.println("返回的结果:" + result);
        Student student = new Student("小张", 23);
        String result2 = studentService.printStudent(student);
        System.out.println("返回的结果:" + result2);
    }
}

好了,来测试一下,先启动我们的服务提供方rpc-sever,再启动我们的服务的消费方rpc-client;
来看一下控制台打印的结果:
rpc-sever:
rpc-sever控制台结果
rpc-client:
rpc-client控制台结果
这下对rpc的过程应该有所了解了吧,本次示例主要利用的java的序列化技术,以及jdk的动态代理来实现的,实际工作中,往往都会使用现成的RPC框架,比如dubbo,motan,grpc等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值