1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

趁热打铁,我们今天来手写一个RPC框架……

前言

昨天大概理了下思路,觉得目前最合适的事,就是手写一个rpc框架,因为只有创造、创作,才让我觉得生活更有激情,而且做具体的事,也会让生活更充实、更真实,会让你不那么迷茫。

如果有同样感受的小伙伴,可以试着找一些具体的事来做一做,这样你也就不再那么浮躁了。

好了,废话少说,我们直接正文。

手写RPC

在开始正文前,我们先看下什么是rpc

关于RPC

rpc的英文全称是Remote Procedure Call,翻译过来就是远程调用,顾名思义,rpc就是一套接口的调用方式,一种调用协议。

关于rpc的调用原理,这里放上百度百科的一张图片,供大家参考:

正如上图所示,因为是远程调用,所以接口的调用方和被调用方是通过网络实现调用和通信的,今天我们是基于socket来实现远程调用的。

rpc广义的定义角度来说,restful也属于远程调用,只是遵循的通信协议不一样。

好了,基础知识就到这里,下面我们看下具体如何实现。

具体实现

开始之前,我想先说下思路。在此之前,我们已经手写过springboot的一个简易框架,通过那个框架,我们了解了服务器的工作流程,和调用过程,当然更重要的是也为我们今天的实现提供思路。简单来说,今天的实现是这样的:

分别创建socket服务端和客户端,他们分别代表服务提供者和服务消费者,服务提供者先启动(服务端),消费者向服务端发送访问请求(包括类名、方法名、参数等),服务提供者收到接口访问请求时,解析请求参数(拿出类名、方法名、参数列表),通过反射调用方法,并将执行结果返回,然后调用完成。

这里是简单的流程图:

下面我们看下具体实现过程。

服务提供者

就是一个socket服务端,这里最核心的问题是,你需要提前确定消费者和提供者要传输哪些数据,因为客户端也是我们自己写,所以具体数据传输就比较灵活。

这里我通过objectInputStreamobjectOutputStream进行数据操作,因为我们操作的都是javaobject,所以用这个就比较方便,当然网上绝大多示例也都是这么实现的。

你直接用InputStream/OutputStream也是可以的,重要的是要理解原理。

这里需要注意的是,服务端读取的顺序,必须和客户端发送的一致,否则在反序列化的时候会报错,会强转失败:

  1. 读取类名
  2. 读取方法名
  3. 读取参数
  4. 读取参数类型
public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            while (true) {
                Socket accept = serverSocket.accept();
                ObjectInputStream objectInputStream = new ObjectInputStream(accept.getInputStream());
                // 读取类名
                String classFullName = objectInputStream.readUTF();
                // 读取方法名
                String methodName = objectInputStream.readUTF();
                // 读取方法调用入参
                Object[] parameters = (Object[])objectInputStream.readObject();
                // 读取方法入参类型
                Class<?>[] parameterTypes = (Class<?>[])objectInputStream.readObject();
                System.out.println(String.format("收到消费者远程调用请求:类名 = {%s},方法名 = {%s},调用入参 = %s,方法入参列表 = %s",
                        classFullName, methodName, Arrays.toString(parameters), Arrays.toString(parameterTypes)));
                Class<?> aClass = Class.forName(classFullName);
                Method method = aClass.getMethod(methodName, parameterTypes);
                Object invoke = method.invoke(aClass.newInstance(), parameters);
                // 回写返回值
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(accept.getOutputStream());
                System.out.println("方法调用结果:" + invoke);
                objectOutputStream.writeObject(invoke);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
接口实现

这里的接口实现就是我们提供给消费者的服务。

public class HelloSercieImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "hello, " + name;
    }
}
服务消费者

前面说了,服务消费者发送消息的顺序必须和服务提供者读取的顺序一致,所以消费者发送的顺序也必须如下:

  1. 写类名
  2. 写方法名
  3. 写参数
  4. 写参数类型
public static void main(String[] args) {
        try {
            String classFullName = "io.github.syske.rpc.service.HelloSercieImpl";
            String methodName = "sayHello";
            String[] parameters = {"syske"};
            Class<?>[] parameterTypes = {String.class};
            Socket socket = new Socket("127.0.0.1", 8888);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectOutputStream.writeUTF(classFullName);
            objectOutputStream.writeUTF(methodName);
            objectOutputStream.writeObject(parameters);
            objectOutputStream.writeObject(parameterTypes);
            // 读取返回值
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            Object o = objectInputStream.readObject();
            System.out.println("消费者远程调用返回结果:" + o);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

这里的方法名和参数列表主要是为了确认方法的唯一性,便于获取方法,后期如果引入facade层,方法名和参数可以直接从接口获取。

测试

我们来测试一下,先启动服务提供者,然后启动服务消费者,这时候会看到服务提供者控制台输出如下信息:

同时,消费者控制台也返回了远程调用结果:

到这里,我们简易的rpc服务框架就基本完成了。怎么样,是不是很简单呢?

总结

不知道大家发现没,虽然我们的rpc框架实现了,但是在调用具体方法的时候很不灵活,不仅需要执行实现类,要指定方法名、入参、参数列表,同时还需要配置服务地址和端口,很繁琐,而且如果我们的远程接口很多,我们就需要配置很多不同的接口信息,接口管理起来就很不方便。

所以这时候我们就应该明白了,为什么rpc框架需要注册中心,以及注册中心的作用?是的,它就是用来注册管理我们的服务的,让我们可以更方便地找到服务的提供者以及接口的信息。

我现在对很多框架了组件有了更深刻的了解和认知,任何组件的出现都是为了解决具体的问题,比如zookeeperredis、配置中心等,但这些组件并非必须的,所以我们在使用具体组件的时候,已经要有知其然,知其所以然的思维,不能仅仅停留在会用的层面,而应该去思考为什么用,为什么不用,只有在这样的思维指导下,你才可能在这个行业走的更远,至少作为一名技术人员,我觉得是这样的。

技术本身只是器,只是工具,脱离技术的思维才是yyds,当然行动也是很重要的,我们也需要不断地实践和探索。

web服务器一样,我以前对rpc的认知也比较浅显,总觉得这些东西很牛逼,但是当我真正理解了原理,然后自己动手做的时候,我觉得也没有那么难,还是那句话,知易行难,还是要自己动手,才能真正了解其中意。

好了,今天就到这里吧!

完整项目开源地址如下,感兴趣的小伙伴可以去看下,后续我们会继续实现相关功能,比如整合注册中心、实现动态代理等待:

https://github.com/Syske/syske-rpc-server
posted @ 2021-06-17 13:42  云中志  阅读(129)  评论(0编辑  收藏  举报