初中生都能看懂的RPC教程——8分钟教你写个RPC框架

8分钟教你写个RPC框架——初中生都能看懂的RPC教程

上一篇理论篇,《初中生都能看懂的RPC教程——3分钟让你搞明白RPC之理论篇》,用三分钟时间讲明白了RPC是干啥的,这一篇实践篇,就为大家展示怎么实现一个RPC,具体原理是啥。

上一篇讲到 计算机A 想去调用 计算机B 的dog.run()方法,具体是怎么做的呢?请看下面:

原理

一个完整的RPC流程,可以用下面这张图来描述:

rpc、RPC、分布式
左边的Client,对应的就是前面的计算机A,而右边的Server,对应的则是计算机 B。``

  • Client
  1. 在 计算机A 的Application代码中,调用了接口Dog的一个实现类的run方法,希望执行一个小狗跑的动作;
  2. 这个Dog实现类,内部并不是直接实现小狗跑的动作,而是通过远程调用 计算机B 的RPC接口,来获取运算结果,称之为Stub;
  3. Stub如何与 计算机B 建立远程通讯呢?这时候就要用到远程通讯工具了,也就是图中的Run-time Library,我们可以用Java的Socket,也可以用基于Http协议的HttpClient,或者其他通讯工具类;
  4. Stub通过调用Socket和计算机B建立起了通讯,然后将请求数据(方法名run、参数100)发送给计算机B。注意,底层通信用的是二进制,我们需要把数据序列化成二进制。
  • Server
  1. 计算机B 收到A发过来的二进制数据,反序列化,也就是计算机B的Run-time Library;
  2. 计算机B 将数据Stub处理,其实Stub并没有做什么,只是解析数据,发现A想调用自己的dog.run(),,参数是100,那么就调用自己的dog.run(100)方法;
  3. 计算机B 执行完dog.run(100)方法,将结果发送给 计算机A,怎么发?同样的流程,只不过现在B变成了Client,A变成了Server。
实践,代码
  • Client端,即计算机A
    Client在应用层发起RPC请求,CilentApp:
public class CilentApp {
    public static void main(String[] args) {
        Dog dog = new DogRemoteImpl();

        //让小狗跑100米,像调用本地方法一样,实际上底层用了RPC调用了另一台计算机上的run()方法
        String result = dog.run(100);
        System.out.println(result);//将收到的结果打印出来
    }
}

DogRemoteImpl是接口Dog的实现类,我们把RPC实现逻辑封装进去了,这样在Client端感知不到RPC的存在,感觉好像是直接调用dog.run();
再来看看DogRemoteImpl类,它实现了接口Dog,

public class DogRemoteImpl implements Dog {
        public String run(int length) {
        String ipAddress = "192.168.1.167";//服务器的ip地址
        int port = 55566;//服务器计算机上,对应的服务器程序的端口
            try {
                Socket socket = new Socket(ipAddress, port);

                // 将请求序列化
                DogRpcRequest dogRpcRequest = new DogRpcRequest("run",length);
                OutputStream out = socket.getOutputStream();//socket输出管道
                //序列化后的内容会传送到 socket输出管道上
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
                // 将dogRpcRequest对象序列化,并将序列化后的内容传到socket输出管道上,通过网络发送出去
                objectOutputStream.writeObject(dogRpcRequest);

                // 等待服务器发来结果,并将结果反序列化
                InputStream in = socket.getInputStream();//socket输入管道,接受传进来的网络信息
                //收到的内容会通过in传到objectInputStream上
                ObjectInputStream objectInputStream = new ObjectInputStream(in);
                Object response = objectInputStream.readObject();//对收到的内容反序列化

                if (response instanceof String) {
                    return (String) response;
                } else {
                    throw new InternalError();
                }
            } catch (Exception e) {
                throw new InternalError();
            }
        }
    }

用Socket进行远程通信,用ObjectOutputStream来序列化对象,用ObjectInputStream实现反序列化,以便接受识别服务器发来的结果,注释写的都很清楚;DogRpcRequest 里面封装了Client端想要请求的方法名和参数值;

再来看看Dog接口和DogRpcRequest 类:

public interface Dog {//接口
    public String run(int length);
}
//DogRpcRequest实现了Serializable接口,只有这样该对象才能序列化
public class DogRpcRequest implements java.io.Serializable{
    public String method;
    public int length;

    public int getLength() {
        return length;
    }
    public String getMethod() {
        return method;
    }
    public DogRpcRequest(String m,int length){
        this.length = length;
        method = m;
    }
}
  • Server端,即计算机B
    来看看server端怎么实现的,和Cilent很类似,ServerApp :
public class ServerApp {
    private Dog dog = new DogImpl();//真正的run()方法在这里面

    public static void main(String[] args) throws IOException {
        new ProviderApp().run();
    }

    private void run() throws IOException {
        ServerSocket listener = new ServerSocket(55566);
        try {
            while (true) {
            	System.out.println("我在监听了...");
                Socket socket = listener.accept();//监听
                try {
                  //服务器端收到内容了,并反序列化
                    ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                    Object object = objectInputStream.readObject();

                    //判断客户端想调用哪个函数
                    String result = null;
                    if (object instanceof DogRpcRequest) {
                        DogRpcRequest dogRpcRequest = (DogRpcRequest) object;
                        if ("run".equals(dogRpcRequest.getMethod())) {//确定想调用run()函数
                            result = dog.run(dogRpcRequest.getLength());//调用真正的run
                        } else {
                            throw new UnsupportedOperationException();
                        }
                    }
                    
                    //将结果序列化,再传回客户端
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                    objectOutputStream.writeObject(result);
                } catch (Exception e) {
                    System.out.println("fail " + e);
                } finally {
                    socket.close();
                }
            }
        } finally {
            listener.close();
        }
    }
}

Server端先用ServerSocket.accept()监听端口,收到请求后,马上反序列化请求->执行->序列化执行结果,最后将二进制格式的执行结果返回给Client;
其中,DogImpl类也是接口Dog的实现类,这里面的run()方法就是Cilent梦寐以求的那个run(),Server帮Cilent调用完了,然后把结果返回给Cilent;
DogImpl类:

public class DogImpl implements Dog{
	//计算机B上的run()方法,真正的run()方法,A梦寐以求的东西
    public String run(int length) {
        return "我跑到终点了,跑了" + length + "米";
    }
}

这样,我们就实现了一个很简单的RPC实例。

最后的思考

我们的这个RPC太简陋了,主要有以下问题:

  1. Server端是单线程
    万一A正在请求调用B上的run,突然又来了个C也想调用B上的run,这时Server就处理不过来了,是否可以考虑在Server端使用多线程来实现,线程池?

  2. 通用性太差
    小狗跑,我们给接口Dog实现了DogRemoteImpl,难道以后有小猫游、小熊飞,我们还要重新写吗?有没有通用的模式呢?值得思考,提示:Dubbo通过和Spring的集成,用到了@Reference注解;

  3. 长连接与短连接、负载均衡…

其实问题有很多,想要实现一个高效稳定的RPC框架,可不是我们随随便便就能写出来的,要考虑的细节和问题有很多,有兴趣的同学可以研究下开源RPC框架,比如:Dubbo、Motan、gRPC等。

理论篇:《初中生都能看懂的RPC教程——3分钟让你搞明白RPC之理论篇

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值