手把手教你写一个基本的Rpc框架

1,289 阅读5分钟

看完点个赞呗,啊呜~

Rpc框架是个啥?

“老王,Rpc是个啥,俺怎么没听说过?” 小王一脸疑惑的问向老王。

“Rpc你都不知道?亏你还是我带出来的。” 老王一脸鄙视的望向小王。

“有那个时间我早去跟学妹深入交流了。” 小王小声嘀咕道。

“好吧,好吧,今天我就跟你讲讲Rpc是个啥,你可得给我好好听。”

“Rpc(Remote Procedure Call)是远程过程调用的意思。举个栗子,部署在两台服务器的服务A1,A2。此时A1想调用A2的服务,采用传统的调用方式是无法调用的。然而Rpc框架解决了这个问题,它可以在不同的系统之间进行通讯。”

“原来如此,老王,那Rpc是咋调用的?” 小王再次问向老王。

“你急个啥,让我慢慢给你道来。”

“Rpc框架实现服务的提供方和消费方。服务的提供方把服务提供出去,让消费方去消费。这里还涉及到服务的发现与注册,这以后再谈”

“至于调用吗,听的再多不如教你写一次”

“老王威武,童颜巨......” 小王对老王夸赞道。
“那是,隔壁老王的称号可不是白叫的”。老王自豪的抬起头,摸了摸头上仅剩的几根头发。

手写基本Rpc

先写暴露服务的方法

暴露服务的方法是将该服务暴露出去,已便于调用

1.1 创建一个暴露服务的方法,这里需要接受两个参数,一个是服务对象,一个是端口号
 public static void exportService(final Object service,int port)
1.2 判断参数是否合法
//判断服务是否为null
if (service==null){
    throw new IllegalArgumentException("service is null");
}
//判断端口是否合法
if (port<=0 || port>65535){
    throw new IllegalArgumentException("Invalid Error, port is not access ");
}
1.3 创建Socket对象
ServerSocket serverSocket = new ServerSocket(port);
//这里要加一个输出,标志服务已经启动
System.out.println("服务已经启动----");
1.4 获取socket,采用线程,创建I/O流
//获取一个socket
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {
    //创建输入输出流
    ObjectInputStream objectInputStream = null;
    ObjectOutputStream objectOutputStream = null;
    @Override
    public void run() {
        }
    }
}).start();
1.5 将对象存入到I/O流中
try {
        objectInputStream = new ObjectInputStream(socket.getInputStream());
        //获取方法名
        String methodName = objectInputStream.readUTF();
        //方法参数类型数组
        Class<?>[] paramTypes = (Class<?>[])objectInputStream.readObject();
        //方法参数
        Object[] arguments = (Object[])objectInputStream.readObject();

        //反射
        Method method = service.getClass().getMethod(methodName, paramTypes);
        Object result = method.invoke(service, arguments);

        //将结果读到输出流中
        objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        objectOutputStream.writeObject(result);
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        try {
            //关闭资源,顺着开,倒着关闭
            objectOutputStream.close();
            objectInputStream.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

“老王,为什么这里是while(true)?这不是死循环了吗?” 小王问老王。
“这你就不懂了吧,这里并不是死循环,而是代表暴露服务的方法一直运行着,每当一次调用请求进来的时候才会执行。”
“这里我们可以用一个requestNum来记录调用的情况”

完整的暴露服务代码

 /**
     * 暴露服务
     * @param service 服务对象
     * @param port 端口
     * @throws Exception
     */
    public static void exportServer(final Object service,int port) throws Exception {
        //判断服务是否为null
        if (service==null){
            throw new IllegalArgumentException("service is null");
        }
        //判断端口是否合法
        if (port<=0 || port>65535){
            throw new IllegalArgumentException("Invalid Error, port is not access ");
        }
        //使用Socket实现
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("服务已经启动----");
        while (true){
            //获取一个socket
            final Socket socket = serverSocket.accept();
            new Thread(new Runnable() {
                //创建输入输出流
                ObjectInputStream objectInputStream = null;
                ObjectOutputStream objectOutputStream = null;
                @Override
                public void run() {
                    try {
                        requestNum++;
                        System.out.println("端口:"+port+"接收请求"+requestNum+"次");
                        objectInputStream = new ObjectInputStream(socket.getInputStream());
                        //获取方法名
                        String methodName = objectInputStream.readUTF();
                        //方法参数类型数组
                        Class<?>[] paramTypes = (Class<?>[])objectInputStream.readObject();
                        //方法参数
                        Object[] arguments = (Object[])objectInputStream.readObject();

                        //反射
                        Method method = service.getClass().getMethod(methodName, paramTypes);
                        Object result = method.invoke(service, arguments);

                        //将结果读到输出流中
                        objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                        objectOutputStream.writeObject(result);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }finally {
                        try {
                            //关闭资源,顺着开,倒着关闭
                            objectOutputStream.close();
                            objectInputStream.close();
                            socket.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }

调用服务的方法

该方法主要用来调用对应的服务

2.1 创建一个调用服务方法,这里需要接受三个参数,接口类型,ip地址,端口号
public static <T> T referServer(final Class<T> interfaceClass,final String host,final int port)
2.2 判断参数合法性
if (interfaceClass ==null){
            throw new IllegalArgumentException("interfaceClass is null");
        }
        if (host ==null){
            throw new IllegalArgumentException("host is null");
        }
        if (port<=0 || port>65535){
            throw new IllegalArgumentException("Invalid port :" + port);
        }
        if (!interfaceClass.isInterface()){
            throw new IllegalArgumentException("interfaceClass is not interface class ");
        }
2.3 采用jdk的动态代理返回服务对象
return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //获取socket
                Socket socket = new Socket(host,port);
                System.out.println("主机ip地址:"+host+",端口号:"+port);
                //创建输入输出流
                ObjectOutputStream objectOutputStream = null;
                ObjectInputStream objectInputStream = null;
                try {
                    //这里采用装饰器模式
                    objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                    //向流中写入方法名,参数类型,参数
                    objectOutputStream.writeUTF(method.getName());
                    objectOutputStream.writeObject(method.getParameterTypes());
                    objectOutputStream.writeObject(args);

                    objectInputStream = new ObjectInputStream(socket.getInputStream());
                    Object result = objectInputStream.readObject();
                    //抛出异常
                    if (result instanceof Throwable) {
                        throw (Throwable)result;
                    }
                    return result;
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    objectInputStream.close();
                    objectOutputStream.close();
                    socket.close();
                }
                return null;
            }
        });

完整的调用服务代码

/**
     *
     * @param interfaceClass 接口类型
     * @param host 主机ip
     * @param port 端口号
     * @param <T> 接口泛型
     * @return 远程服务对象
     * @throws Exception
     */
    public static <T> T referServer(final Class<T> interfaceClass,final String host,final int port)throws Exception{
        if (interfaceClass ==null){
            throw new IllegalArgumentException("interfaceClass is null");
        }
        if (host ==null){
            throw new IllegalArgumentException("host is null");
        }
        if (port<=0 || port>65535){
            throw new IllegalArgumentException("Invalid port :" + port);
        }
        if (!interfaceClass.isInterface()){
            throw new IllegalArgumentException("interfaceClass is not interface class ");
        }

        return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //获取socket
                Socket socket = new Socket(host,port);
                System.out.println("主机ip地址:"+host+",端口号:"+port);
                //创建输入输出流
                ObjectOutputStream objectOutputStream = null;
                ObjectInputStream objectInputStream = null;
                try {
                    //这里采用装饰器模式
                    objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                    //向流中写入方法名,参数类型,参数
                    objectOutputStream.writeUTF(method.getName());
                    objectOutputStream.writeObject(method.getParameterTypes());
                    objectOutputStream.writeObject(args);

                    objectInputStream = new ObjectInputStream(socket.getInputStream());
                    Object result = objectInputStream.readObject();
                    //抛出异常
                    if (result instanceof Throwable) {
                        throw (Throwable)result;
                    }
                    return result;
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    objectInputStream.close();
                    objectOutputStream.close();
                    socket.close();
                }
                return null;
            }
        });
    }

测试手写的Rpc框架

“小王啊,我们来测试一下吧”

创建服务接口以及实现类

public interface MyInterface {

    String hello();
}
public class MyInterfaceImpl implements MyInterface {
    @Override
    public String hello() {
        return "HELLO WORLD";
    }
}

启动两个main方法进行测试

public class MyTest {
    public static void main(String[] args) throws Exception {
        //创建服务
        MyInterfaceImpl server = new MyInterfaceImpl();
        //暴露服务
        MyRpcFramework.export(server,6000);
    }
}
public class MyTest2 {
    public static void main(String[] args) throws Exception {
        //调用服务
        MyInterface server = MyRpcFramework.refer(MyInterface.class, "127.0.0.1", 6000);
        for (int i = 0;i<10;i++){
            System.out.println(server.hello()+"--"+i);
            Thread.sleep(1000);
        }
    }
}

测试结果

总结

  以上便是手写基本Rpc框架的全部内容,作者希望采用小王和老王的对话来更加生动的描写出技术原理以及实现,感谢大家的阅读。天高地远,我们江湖再见!