动手实现一个简单的RPC

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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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等。

介绍RCP的实现原理 目录 1. 前言 2 2. 基本概念 3 2.1. IDL 3 2.2. 代理(Proxy) 3 2.3. 存根(Stub) 4 3. 三要素 4 3.1. 网络通讯 4 3.2. 消息编解码 5 3.3. IDL编译器 5 4. flex和bison 5 4.1. 准备概念 5 4.1.1. 正则表达式(regex/regexp) 6 4.1.2. 符号∈ 6 4.1.3. 终结符/非终结符/产生式 6 4.1.4. 记号(Token) 6 4.1.5. 形式文法 7 4.1.6. 上下文无关文法(CFG) 7 4.1.7. BNF 8 4.1.8. 推导 8 4.1.9. 语法树 8 4.1.10. LL(k) 9 4.1.11. LR(k) 9 4.1.12. LALR(k) 9 4.1.13. GLR 9 4.1.14. 移进/归约 9 4.2. flex和bison文件格式 9 4.2.1. 定义部分 10 4.2.2. 规则部分 10 4.2.3. 用户子例程部分 10 4.3. flex基础 10 4.3.1. flex文件格式 11 4.3.2. 选项 11 4.3.3. 名字定义 11 4.3.4. 词法规则 12 4.3.5. 匹配规则 12 4.3.6. %option 13 4.3.7. 全局变量yytext 13 4.3.8. 全局变量yyval 13 4.3.9. 全局变量yyleng 13 4.3.10. 全局函数yylex 13 4.3.11. 全局函数yywrap 13 4.4. bison基础 14 4.4.1. bison文件格式 14 4.4.2. %union 14 4.4.3. %token 15 4.4.4. 全局函数yyerror() 15 4.4.5. 全局函数yyparse() 15 4.5. 例1:单词计数 15 4.5.1. 目的 15 4.5.2. flex词法文件wc.l 16 4.5.3. Makefile 16 4.6. 例2:表达式 17 4.6.1. 目的 17 4.6.2. flex词法exp.l 17 4.6.3. bison语法exp.y 17 4.6.4. Makefile 19 4.6.5. 代码集成 19 4.7. 例3:函数 20 4.7.1. 目的 20 4.7.2. func.h 20 4.7.3. func.c 21 4.7.4. IDL代码func.idl 22 4.7.5. flex词法func.l 22 4.7.6. bison语法func.y 24 4.7.7. Makefile 27 5. 进阶 27 5.1. 客户端函数实现 27 5.2. 服务端函数实现 28 5.2.1. Stub部分实现 28 5.2.2. 用户部分实现 29 6. 参考资料 29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值