什么?手写RPC只需两百行代码!

回顾RPC框架

preview

节点 角色说明
Provider 提供远程服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心

Dubbo最重要的三个角色就是服务提供方、服务消费方、注册中心。

下面我们通过Java原生API实现远程调用,不使用任何第三方框架!

服务提供方

package rpc.provider;

/**
 * 服务提供方 接口
 *
 * @author zab
 * @date 2020-05-16 17:51
 */
public interface ProviderService {
    String testMethod(String ping);
}
package rpc.provider;

/**
 * 服务提供方 实现
 *
 * @author zab
 * @date 2020-05-16 17:52
 */
public class ProviderServiceImpl implements ProviderService {
    @Override
    public String hello(String str) {
        return str == null ? "hello consumer." : str + "---> hello consumer. I can do something else for you!";
    }
}

服务“注册中心”

这里的注册中心是写死的,只是描述原理,功能很死板。

package rpc.server;

import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * RPC服务端,将provider的服务发布成远程服务,供消费者调用
 *
 * @author zab
 * @date 2020-05-16 17:55
 */
public class RpcServer {

    /**
     * 自定义线程池,自定义线程工厂和拒绝策略
     */
    static Executor executor = new ThreadPoolExecutor(10, 10, 0,
            TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
            r -> {
                Thread t = new Thread(r);
                t.setName("myThread");
                return t;
            },
            (r, executor) -> System.out.println(r.toString() + " is discard")
    );

    /**
     * 处理RPC请求调用
     */
    public static void startServer(String hostName, int port) throws Exception {
        ServerSocket server = new ServerSocket();

        server.bind(new InetSocketAddress(hostName, port));
        try {
            while (true) {
                executor.execute(new RpcHandleTask(server.accept()));
            }
        } finally {
            server.close();
        }
    }

    /**
     * 接收TCP数据,根据接口,反射调用接口实现类provider的方法
     */
    private static class RpcHandleTask implements Runnable {
        Socket socket = null;

        public RpcHandleTask(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            ObjectInputStream input = null;
            ObjectOutputStream output = null;
            try {
                input = new ObjectInputStream(socket.getInputStream());
                //读出接口名
                String interfaceName = input.readUTF();
                //找到接口所在相对地址
                String path = interfaceName.substring(0, interfaceName.lastIndexOf("."));

                Class<?> clazz = Class.forName(interfaceName);

                //找到接口所在文件下所有文件
                File[] files = new File(clazz.getResource("/").getFile() + path.replaceAll("\\.", "\\/")).listFiles();
                for (File file : files) {
                    //找文件,而不是文件夹
                    if (!file.isDirectory()) {
                        //path不一定是provider实现类的,这里需要实现类的path
                        String implPath = path + "." + file.getName().replaceAll(".class", "");
                        //获取实现类的class
                        Class<?> implClass = Class.forName(implPath);
                        //不是接口,并且实现类的接口名和传过来的接口名一致
                        if (!implClass.isInterface() && interfaceName.equals(implClass.getInterfaces()[0].getName())) {
                            //获取消费者传过来的接口方法
                            String methodName = input.readUTF();
                            //获取消费者传过来的接口参数类型
                            Class<?>[] parameterTypes = (Class<?>[]) input.readObject();

                            Method method = implClass.getMethod(methodName, parameterTypes);

                            Object[] arguments = (Object[]) input.readObject();
                            //移花接木,调用实现类的方法
                            Object result = method.invoke(implClass.newInstance(), arguments);

                            output = new ObjectOutputStream(socket.getOutputStream());
                            output.writeObject(result);
                        }
                    }
                }


            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (output != null) {
                    try {
                        output.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                if (input != null) {
                    try {
                        input.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            }
        }
    }
}

服务消费方

package rpc;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * 服务消费者远程调用服务提供方,处理连接、反序列化之类工作
 *
 * @author zab
 * @date 2020-05-16 16:50
 */
public class RpcConsumer {

    public Object remoteCall(final Class clazz, final InetSocketAddress addr,Object[] args) throws Throwable{
        Socket socket = null;
        ObjectOutputStream output = null;
        ObjectInputStream input = null;
        try {
            socket = new Socket();
            socket.connect(addr);
            output = new ObjectOutputStream(socket.getOutputStream());

            output.writeUTF(clazz.getName());

            Method[] methods = clazz.getMethods();
            for (Method method : methods) {

                output.writeUTF(method.getName());

                output.writeObject(method.getParameterTypes());

                output.writeObject(args);
            }

            input = new ObjectInputStream(socket.getInputStream());
            return input.readObject();
        } finally {
            if (socket != null) socket.close();
            if (output != null) output.close();
            if (input != null) input.close();
        }
    }
}

测试

package rpc;

import rpc.provider.ProviderService;
import rpc.server.RpcServer;

import java.net.InetSocketAddress;

public class Test {

    static {
        startServer();
    }

    public static void main(String[] args) throws Throwable {

        RpcConsumer consumer = new RpcConsumer();

        Object[] objArgs = {"调用远程方法--"};

        Object providerService = consumer.remoteCall(ProviderService.class, new InetSocketAddress("localhost", 8083), objArgs);

        System.out.println(providerService.toString());

    }

    private static void startServer() {
        new Thread(new Test()::runServer).start();
    }

    private void runServer() {
        try {
            //远程TCP服务打开,provide发布到远程
            RpcServer.startServer("localhost", 8083);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试结果

技术分析

1、测试类:通过静态代码块调用独立线程启动服务。可以发现,服务消费方,只需要知道服务提供方的接口和地址端口,即可工作。

2、服务消费方:通过socket连接服务注册中心,传入要调用哪个接口、什么参数、何种方法。最后就拿到socket返回的输入流

3、服务注册中心:通过自定义线程池来处理连接,注册中心作为一个socket服务端,根据客户端传过来的接口反射获取接口路径,找到同路径下的该接口实现类,根据接口方法名,调用实现类的方法,最后把结果写入流,通过socket回写给服务消费方。

4、服务提供方:很简单,就一个hello方法。

阅读本案例代码,需要你掌握哪些java基础知识?

1、如何自定义线程池

2、反射原理

3、网络编程

4、Java8新特性

偷个懒,代码拷起来太麻烦!github链接在此:

https://github.com/Jackson-zhanganbing/message-middleware-demo.git

 

展开阅读全文
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值