emmm,昨天蘑菇街一面,我感觉面试官特别好,最后的时候给了我一些建议和方向,感觉启发很大。面试过程中,问了我几个相对开放的问题,没怎么问基础。但是我感觉我答的不很好,第一次面大公司有点紧张。希望过过过!!!其中有一个题,面试官说你手动搭建过springboo + zookeeper + dubbo是吧,那么dubbo的原理是怎样的呢,或者你手动搭建一个dubbo你想这么做呢?基于这个原因,我今天就花费了半天的时间通过查资料做了一个远程调用rpc。

首先呢,我们的这个项目是基于netty、动态代理、反射等知识实现的,如果童鞋们对这块内容还有不熟的地方,建议去先了解一下这些知识,再来看这篇文章,我觉得才有意义。

项目结构:

这里有三个module,先说common,它里面主要包含了我们的client和server共同的东西(一会细说),它是以依赖形式在我们的其他两个子项目中的pom.xml里的。server,里面包含了我们的一个服务器,具体的serviceimpl实现的类,client,里面包含了我们的客户端。这两个就是client调用的server的方法(注意这里不是通过rest接口实现的)。这两个项目可以独立部署在服务器上也是可以的。

common

这里的ClassInfo是我们要传递的类的信息,一会大家就很清楚了(悄悄的透露一下,就是下面两个service的方法具体信息)。它是建立我们调用的核心!!!下面两个service就是我们要远程调用他们的实现类的。

package com.rpc.common.enity;import lombok.Data;import java.io.Serializable;@Data
public class ClassInfo implements Serializable {private String className;private String methodName;private Class<?>[] types;private Object[] objects;}

这个老哥就详细的记录着我们调用方法的接口方法的类名、方法名、参数类型和参数s。奥力给!!

package com.rpc.common.service;public interface HelloService {String helloRPC();
}
package com.rpc.common.service;ublic interface HiService {String HiRPC(String name);
}

这两个就是普通的接口,就是我们在dubbo框架中熟悉的@Service下面的接口。在dubbo里就是通过这个注册中心注册的。

Server

接下来我们再一起康康Server

先看看这两个没啥好说的实现类吧

package com.rpc.server.impl;import com.rpc.common.service.HelloService;public class HelloServiceImpl implements HelloService {@Overridepublic String helloRPC() {return "helloRPC";}
}package com.rpc.server.impl;import com.rpc.common.service.HiService;public class HiServiceImpl implements HiService {@Overridepublic String HiRPC(String name) {return "Hi" + name;}
}

然后就是我们的netty啦,真的是方便又安全的框架!!

package com.rpc.server.netty;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;public class NettyServer {private int port;EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();public NettyServer(int port){this.port = port;}public void start(){try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true).localAddress(port).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();//编码器pipeline.addLast("encoder",new ObjectEncoder());//解码器pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE,ClassResolvers.cacheDisabled(null)));//服务器端业务处理类pipeline.addLast(new InvokeHandle());}});ChannelFuture future = serverBootstrap.bind(port).sync();System.out.println("服务启动-------------");future.channel().closeFuture().sync();} catch (Exception e) {bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully();}}public static void main(String[] args) {new NettyServer(9999).start();}
}

这一大串我就不具体说了,如果接触过netty的小盆友,因该都不会陌生。编码和解码,就是对我们传过来的对象进行解码或者编码,其实就是序列化。netty已经帮我们做好了。

package com.rpc.server.netty;import com.rpc.common.enity.ClassInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.reflections.Reflections;import java.lang.reflect.Method;
import java.util.Set;public class InvokeHandle extends ChannelInboundHandlerAdapter {private static String interfacePath = "com.rpc.common.service";private static String implPath="com.rpc.server.impl";private String getImplClassName(ClassInfo classInfo)throws Exception{int lastDot = classInfo.getClassName().lastIndexOf(".");String interfaceName = classInfo.getClassName().substring(lastDot);Class superClass = Class.forName(interfacePath + interfaceName);Reflections reflections = new Reflections(implPath);//得到接口下面的实现类Set<Class> implClassSet = reflections.getSubTypesOf(superClass);if(implClassSet.size() == 0){System.out.println("未找到实现类");return null;}else if(implClassSet.size() > 1){System.out.println("找到多个实现类");return null;}else{Class[] classes = implClassSet.toArray(new Class[0]);return classes[0].getName();//实现类的名字}}@Overridepublic void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception {ClassInfo classInfo = (ClassInfo)msg;Object clazz = null;try {clazz = Class.forName(getImplClassName(classInfo)).newInstance();} catch (Exception e) {e.printStackTrace();System.out.println("类名提取异常");}Method method = clazz.getClass().getMethod(classInfo.getMethodName(),classInfo.getTypes());Object result = method.invoke(clazz,classInfo.getObjects());ctx.writeAndFlush(result);ctx.close();}}

咱们可以用channelRead这个方法直接接收到客户端发过来的请求。也就是msg。实际上已经帮我们解码好了,重写反序列化成对象了,我们直接拿就好了,然后我们通过getImplClassName方法获得这个类的实现类的Class对象,我们姑且不看这个getImplClassName方法,假设我们拿到这个实现类了,就是咱们server下面的impl这些,我们就可以对应调用的方法,然后invoke调用。最后返回处理结果。这个调用任务就完成了。

接下来我们看getImplClassName方法。它通过forName拿到service的class对象,然后用下面代码:

 Reflections reflections = new Reflections(implPath);//得到接口下面的实现类
Set<Class> implClassSet = reflections.getSubTypesOf(superClass);

获得所有的实现类。最终经过处理能够得到实现类。这也就完成了我们服务端调用的整个过程。我们接下来关注的就是客户端如何把ClassInfo给我们发过来。

Client

我们首先来看一下代理对象:

package com.rpc.client.proxy;import com.rpc.client.netty.NettyClient;
import com.rpc.common.enity.ClassInfo;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class NettyRPCProxy implements InvocationHandler {private static Class clazz;public static Object create(Class target){clazz = target;return Proxy.newProxyInstance(target.getClassLoader(),new Class[]{target},new NettyRPCProxy());}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//封装ClassinfoClassInfo classInfo = new ClassInfo();classInfo.setClassName(clazz.getName());classInfo.setMethodName(method.getName());classInfo.setObjects(args);classInfo.setTypes(method.getParameterTypes());//开始netty发送数据return new NettyClient().start(classInfo);}}

create方法创建代理对象就不说了啊,大家想没想我们为啥要用代理呀?代理可以获取我们所有的方法的参数、类型、以及方法名。这就是它存在的意义。这些都是我们传输ClassInfo对象的字段,必须品!!!

package com.rpc.client.netty;import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;public class ResultHandler extends ChannelInboundHandlerAdapter {private Object response;public Object getResponse() { return response; }@Override //读取服务器端返回的数据(远程调用的结果)public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {response = msg;ctx.close();}}

这个就不复杂了,直接是获取服务端调用完方法的结果!

package com.rpc.client.netty;import com.rpc.client.proxy.NettyRPCProxy;
import com.rpc.common.enity.ClassInfo;
import com.rpc.common.service.HelloService;
import com.rpc.common.service.HiService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;public class NettyClient {private  EventLoopGroup group = new NioEventLoopGroup();private ResultHandler resultHandler =  new ResultHandler();public Object start(ClassInfo classInfo){try {Bootstrap client = new Bootstrap();client.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();//编码器pipeline.addLast("encoder",new ObjectEncoder());//解码器pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE,ClassResolvers.cacheDisabled(null)));//服务器端业务处理类pipeline.addLast("handle",resultHandler);}});ChannelFuture future = client.connect("127.0.0.1",9999).sync();future.channel().writeAndFlush(classInfo).sync();future.channel().closeFuture().sync();} catch (Exception e) {}finally {group.shutdownGracefully();}return resultHandler.getResponse();}public static void main(String[] args) {//第一次调用HelloService helloService = (HelloService) NettyRPCProxy.create(HelloService.class);System.out.println(helloService.helloRPC());//第二次调用HiService hiService = (HiService) NettyRPCProxy.create(HiService.class);System.out.println(hiService.HiRPC("baby"));}
}

这就是我们调用的过程了!!!,实际上大家可以想一想,dubbo的zookeeper的注册中心就是注册了一个service的信息,还有域名端口,我们这里是在项目中写固定了这些东西,ClassInfo很重要。由于时间紧,代码规范不是很好,大家见谅。

运行结果:

最后配上一个小图,大家在看就理解了!

过程:

client stub可以是bio、nio和netty。

1、服务消费方(client)以本地调用方式调用服务。

2、client stub接收到调用后负责将方法、参数封装成能够进行网络传输的消息体

3、client stub将消息进行编码并发送到服务端

4、server stub接收到消息后进行解码

5、server stub根据解码j结果调用本地的服务

6、本地服务执行并将结果返回给server stub

7、server stub将返回结果进行编码并发送至消费方

8、client stub接收到消息并进行解码

9、服务消费方client得到结果。

而dubbo做的就是封装了2-8。

项目地址:git@github.com:Zesystem/-Handwritten-RPC.git

自己手写一个RPC,实现远程调用功能(基于netty、反射和代理)相关推荐

  1. 手写一个 RPC 远程调用(C++)

    手写一个 RPC 远程调用(C++) 版权声明 个人作品,禁止转载 参考资料 Build Remote Procedure Calls (RPC) - from scratch in C(https: ...

  2. 【RPC框架、RPC框架必会的基本知识、手写一个RPC框架案例、优秀的RPC框架Dubbo、Dubbo和SpringCloud框架比较】

    一.RPC框架必会的基本知识 1.1 什么是RPC? RPC(Remote Procedure Call --远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术. ...

  3. 手写一个RPC框架,理解更透彻(附源码)

    作者:烟味i www.cnblogs.com/2YSP/p/13545217.html 一.前言 前段时间看到一篇不错的文章<看了这篇你就会手写RPC框架了>,于是便来了兴趣对着实现了一遍 ...

  4. 面试官让我手写一个RPC框架

    如今,分布式系统大行其道,RPC 有着举足轻重的地位.Dubbo.Thrift.gRpc 等框架各领风骚,学习RPC是新手也是老鸟的必修课.本文带你手撸一个rpc-spring-starter,深入学 ...

  5. Marco's Java【Dubbo 之手写Dubbo框架实现远程调用】

    前言 关于Dubbo入门的网上教程也特别多,因此我没有专门出关于Dubbo的系列博文(主要呢- 也是在忙些工作上的事儿),用Dubbo特别简单,但是想要把Dubbo学好,学精还得花费不少时间的,特别是 ...

  6. 如何手写一个RPC(面试要知道)

    文章目录 一.RPC 到底是什么? 二.场景的模拟 三.rpc-core项目 四.谁应该实现接口? 五.室友室友这个类来计算答案 六.对自己的改造(关键的一步) 七.使用注册中心来解决室友端口变化的问 ...

  7. 【手写一个RPC框架】simpleRPC-04

    目录 前言 实现 项目创建 配置依赖 common service server client 文件结构 运行 本项目所有代码可见:https://github.com/weiyu-zeng/Simp ...

  8. 【手写一个RPC框架】simpleRPC-05

    目录 前言 实现 项目创建 依赖配置 common service codec client server 文件结构 运行 本项目所有代码可见:https://github.com/weiyu-zen ...

  9. 手写一个简易版本的RPC

    前言 在1024程序员节前夕,学习技术是对节日最好的庆祝. 手写一个简易版的RPC,可以把模糊抽象的概念具像化,属于落地层面了. 1. RPC基本原理 RPC原理 2. 四个版本的迭代 注:api表示 ...

最新文章

  1. python计算平方面积_python中求平方
  2. java语言提供结构_java学习之语句结构
  3. mysql测试spring事务是否生效
  4. JVM_07 Class文件结构
  5. 牛客 - Gaming with Mia(dp)
  6. Android之jni日志如何输出
  7. django时间问题和时区设置
  8. 程序员必知--代码规范
  9. msys2软件包管理工具pacman常用命令
  10. 远程调试运行在Resin上面的Web应用程序
  11. mvc报错:403.14-Forbidden Web 服务器被配置为不列出此目录的内容
  12. 8086c语言编译器,8086汇编语言编译器MKStudio安装使用教程
  13. Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java15 的新特性
  14. tablepc是什么平板电脑_Tablet PC,这是什么意思?
  15. 虫儿飞简谱用计算机,乐曲简谱(虫儿飞简谱)
  16. Oracle一备份内存就占满卡死,rman备份占用内存问题
  17. 1906: 鹊桥相会
  18. (一)OSG初学者入门基础教程
  19. 数据中台Citus集群压测报告
  20. js单行代码------对象

热门文章

  1. 2021年度总结——熬了3 年的芯片今晚来了
  2. qt 多线程、信号槽、moveToThread等机制之拨乱反正
  3. Nginx的部署与配置
  4. 前端 - 博客系统(页面设计)
  5. PAP与CHAP认证
  6. 2019上海交大计算机考研群,2019年科班二战上海交大计算机专硕,调剂非全初复试经验教训分享!...
  7. 快手sig3,did设备注册算法
  8. IO流 输入流、输出流、字节流、字符流、转换流、及Properties类
  9. 程序设计竞赛学习总结
  10. 远光九天云平台 自主创新助力科技自强