RPC框架—傻瓜式教程(一)

前言,太久没写博客了,有点手生,总结一下自己对RPC框架的学习过程


首先我们知道RPC的全名是,全程服务调用,我们用它来做什么,简单地说就是客户端通过接口调用服务端的函数或者方法。

但是这个看起来很简单的事情,还需要我们思考很多,序列化和反序列化,还有协议的约定,一大堆。

所以要想深入了解RPC框架,我们还要从浅到深,一点一点实现它。

开发环境:
windows10
IDEA
Maven

前期准备

首先我们创建一个maven项目,在pom.xml里导入我们的dependency,这一步都不会的朋友我就不教了,建议先去学学maven(较简单)

    //注释部分,复制后记得把这段文字删掉,这部分是我们项目里的modules,要导入,实际上刚开始你复制上去会报红,是因为我们还没有创建module,不用管,创建完成就解决了<modules><module>RPC-common</module><module>RPC-core</module><module>RPC-api</module><module>Test-server</module><module>Test-client</module></modules><properties><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><netty-version>4.1.50.Final</netty-version><guava.version>29.0-jre</guava.version></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.30</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.30</version></dependency></dependencies>

然后我们创建module,file->new->module
创建这几个模块

接口的实现

首先创建我们服务端和客户端之间的接口

HelloService.java

/*** 测试api调用的接口*/
public interface HelloService {String hello(HelloObject object);}

我们在客户端实现它,我们可以看到这个类实现了Serializable接口,因为我们要把由它创建的对象从客户端传到服务端,所以需要序列化

HelloObject.java

/*** 测试api调用的实体*/
@Data
@AllArgsConstructor
@NoArgsConstructor
//这三个注解是lombok的注解,自动创捷set get constructer的,
//如果报红记得在setting->plugins里下载插件
public class HelloObject implements Serializable {private Integer id;private String message;
}

接着我们在服务端实现它

HelloServiceImpl.java

public class HelloServiceImpl implements HelloService {private static final Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);@Overridepublic String hello(HelloObject object) {logger.info("服务端接收到:{}", object.getMessage());return "这是调用的返回值,id=" + object.getId();}
}

传输

-接口实现了之后我们来实现中间的传输部分,这里看到的其他类先不用创建,按顺序走就是了

  • 客户端发起一个调用请求我们服务端怎么知道它想要调用的是哪个接口哪个方法呢?首先,我们需要知道接口的名字,和方法的名字,但是由于方法重载的缘故,我们还需要这个方法的所有参数的类型,最后,客户端调用时,还需要传递参数的实际值。
    那么服务端知道以上四个条件,就可以找到这个方法并且调用了。我们把这四个条件写到一个对象里,到时候传输时传输这个对象就行了。
    RpcRequest.java
@Data
@Builder
public class RpcRequest implements Serializable {/*** 待调用接口名称*/private String interfaceName;/*** 待调用方法名称*/private String methodName;/*** 调用方法的参数*/private Object[] parameters;/*** 调用方法的参数类型*/private Class<?>[] paramTypes;
}
  • 服务器调用完这个方法后,需要给客户端返回信息呢。如果调用成功的话,显然需要返回值,如果调用失败了,就需要失败的信息,这就我们的RpcResponse
    RpcResponse.java
@Data
public class RpcResponse<T> implements Serializable {/*** 响应状态码*/private Integer statusCode;/*** 响应状态补充信息*/private String message;/*** 响应数据*/private T data;public static <T> RpcResponse<T> success(T data) {RpcResponse<T> response = new RpcResponse<>();response.setStatusCode(ResponseCode.SUCCESS.getCode());response.setData(data);return response;}public static <T> RpcResponse<T> fail(ResponseCode code) {RpcResponse<T> response = new RpcResponse<>();response.setStatusCode(code.getCode());response.setMessage(code.getMessage());return response;}
}
  • 我们把成功的返回值写成一个枚举类
    ResponseCode.java
@AllArgsConstructor
@Getter
public enum ResponseCode {SUCCESS(200, "调用方法成功"),FAIL(500, "调用方法失败"),METHOD_NOT_FOUND(500, "未找到指定方法"),CLASS_NOT_FOUND(500, "未找到指定类");private final int code;private final String message;}
  • 再把失败的消息写成一个枚举类
@AllArgsConstructor
@Getter
public enum RpcError {UNKNOWN_ERROR("出现未知错误"),SERVICE_SCAN_PACKAGE_NOT_FOUND("启动类ServiceScan注解缺失"),CLIENT_CONNECT_SERVER_FAILURE("客户端连接服务端失败"),SERVICE_INVOCATION_FAILURE("服务调用出现失败"),SERVICE_NOT_FOUND("找不到对应的服务"),SERVICE_NOT_IMPLEMENT_ANY_INTERFACE("注册的服务未实现接口"),UNKNOWN_PROTOCOL("不识别的协议包"),UNKNOWN_SERIALIZER("不识别的(反)序列化器"),UNKNOWN_PACKAGE_TYPE("不识别的数据包类型"),SERIALIZER_NOT_FOUND("找不到序列化器"),RESPONSE_NOT_MATCH("响应与请求号不匹配"),FAILED_TO_CONNECT_TO_SERVICE_REGISTRY("连接注册中心失败"),REGISTER_SERVICE_FAILED("注册服务失败");private final String message;
}

然后我们的传输过程就约定好了

客户端


这个module里的pom.xml

<dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>${netty-version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.11.0</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.11.0</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.11.0</version></dependency><dependency><groupId>com.esotericsoftware</groupId><artifactId>kryo</artifactId><version>4.0.2</version></dependency><dependency><groupId>com.caucho</groupId><artifactId>hessian</artifactId><version>4.0.63</version></dependency><dependency><groupId>io.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.7.2</version></dependency><dependency><groupId>io.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.7.2</version></dependency><dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId><version>1.3.0</version></dependency><dependency><groupId>org.example</groupId><artifactId>RPC-test</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency><dependency><groupId>org.example</groupId><artifactId>RPC-test</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency></dependencies>

客户端的实现,当然是使用动态代理了,不会动态代理的同学继续补课。
这里我们采用JDK动态代理

RpcClientProxy.java
我们需要传递host和port来指明服务端的位置。并且使用getProxy()方法来生成代理对象。

InvocationHandler接口需要实现invoke()方法,来指明代理对象的方法被调用时的动作。在这里,我们显然就需要生成一个RpcRequest对象,发送出去,然后返回从服务端接收到的结果即可:

/*** RPC客户端动态代理*/
public class RpcClientProxy implements InvocationHandler {private String host;private int port;public RpcClientProxy(String host, int port) {this.host = host;this.port = port;}@SuppressWarnings("unchecked")public <T> T getProxy(Class<T> clazz) {return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//这里使用builder()方法来生成这个对象(RpcRequest类里有Builder注解)RpcRequest rpcRequest = RpcRequest.builder().interfaceName(method.getDeclaringClass().getName()).methodName(method.getName()).parameters(args).paramTypes(method.getParameterTypes()).build();RpcClient rpcClient = new RpcClient();return ((RpcResponse) rpcClient.sendRequest(rpcRequest, host, port)).getData();}
}

RpcClient

/***发送的逻辑我使用了一个RpcClient对象来实现这个对象的作用,就是将一个对象发过去,并且接受返回的对象*/
public class RpcClient {private static final Logger logger = LoggerFactory.getLogger(RpcClient.class);public Object sendRequest(RpcRequest rpcRequest, String host, int port) {try (Socket socket = new Socket(host, port)) {ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());objectOutputStream.writeObject(rpcRequest);objectOutputStream.flush();return objectInputStream.readObject();} catch (IOException | ClassNotFoundException e) {logger.error("调用时有错误发生:", e);return null;}}
}

服务端

服务端的实现就简单多了,使用一个ServerSocket监听某个端口,循环接收连接请求,如果发来了请求就创建一个线程(BIO模式,后面会修改成NIO),在新线程中处理调用。这里创建线程采用线程池:

public class RpcServer {private final ExecutorService threadPool;private static final Logger logger = LoggerFactory.getLogger(RpcServer.class);public RpcServer() {int corePoolSize = 5;int maximumPoolSize = 50;long keepAliveTime = 60;BlockingQueue<Runnable> workingQueue = new ArrayBlockingQueue<>(100);ThreadFactory threadFactory = Executors.defaultThreadFactory();threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workingQueue, threadFactory);}/*这里简化了一下,RpcServer暂时只能注册一个接口,即对外提供一个接口的调用服务,添加register方法,在注册完一个服务后立刻开始监听*/public void register(Object service, int port) {try (ServerSocket serverSocket = new ServerSocket(port)) {logger.info("服务器正在启动...");Socket socket;while((socket = serverSocket.accept()) != null) {logger.info("客户端连接!Ip为:" + socket.getInetAddress());threadPool.execute(new WorkerThread(socket, service));}} catch (IOException e) {logger.error("连接时有错误发生:", e);}}/*这是个内部类,是我们的工作线程这里向工作线程WorkerThread传入了socket和用于服务端实例service。WorkerThread实现了Runnable接口,用于接收RpcRequest对象,解析并且调用,生成RpcResponse对象并传输回去*/private class WorkerThread implements Runnable {public WorkerThread(Socket socket, Object service) {}@Overridepublic void run() {try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());Object returnObject = method.invoke(service, rpcRequest.getParameters());objectOutputStream.writeObject(RpcResponse.success(returnObject));objectOutputStream.flush();} catch (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {logger.error("调用或发送时有错误发生:", e);}}}
}

测试

完成了接口,传输,客户端,服务端的设计后,我们模拟一个客户端一个服务端来尝试使用我们的简略版RPC

Client-Test
客户端方面,我们需要通过动态代理,生成代理对象,并且调用,动态代理会自动帮我们向服务端发送请求的

public class TestClient {public static void main(String[] args) {RpcClientProxy proxy = new RpcClientProxy("127.0.0.1", 8888);HelloService helloService = proxy.getProxy(HelloService.class);HelloObject object = new HelloObject(888, "This is a message");String res = helloService.hello(object);System.out.println(res);}
}

Server-Test
服务端侧,服务端开放在9000端口。我们已经在上面实现了一个HelloService的实现类HelloServiceImpl的实现类了,我们只需要创建一个RpcServer并且把这个实现类注册进去就行了:

public class TestServer {public static void main(String[] args) {HelloService helloService = new HelloServiceImpl();RpcServer rpcServer = new RpcServer();rpcServer.register(helloService, 8888);}
}

先启动服务端,再启动客户端,奇迹就出现了
over~

手撸一个RPC框架——傻瓜式教程(一)相关推荐

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

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

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

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

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

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

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

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

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

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

  6. 如何手撸一个较为完整的RPC框架

    [文章作者/来源]一个没有追求的技术人/https://sourl.cn/sJ4Brp 缘 起 最近在公司分享了手撸RPC,因此做一个总结. 概 念 篇 RPC 是什么? RPC 称远程过程调用(Re ...

  7. 如何手撸一个较为完整的RPC框架?

    点击关注公众号,实用技术文章及时了解 来源:juejin.cn/post/6992867064952127524 缘起 最近在公司分享了手撸RPC,因此做一个总结. 概念篇 RPC 是什么? RPC ...

  8. 经典项目|手撸一个高质量RPC框架

    hi, 大家好,RPC是后端系统节点之间通信的核心技术,属于后端开发必须要学习的技能. 后端技术趋势指南|如何选择自己的技术方向 如何从0搭建公司的后端技术栈 远程过程调用(Remote Proced ...

  9. 很多小伙伴不太了解ORM框架的底层原理,这不,冰河带你10分钟手撸一个极简版ORM框架(赶快收藏吧)

    大家好,我是冰河~~ 最近很多小伙伴对ORM框架的实现很感兴趣,不少读者在冰河的微信上问:冰河,你知道ORM框架是如何实现的吗?比如像MyBatis和Hibernate这种ORM框架,它们是如何实现的 ...

最新文章

  1. java which语句,java入门之表达式、语句、块
  2. Spring学习笔记:2(IOC装配Bean之xml方式)
  3. vb.net调用oracle存储过程,今天搞好了VB.NET调用Oracle存储过程返回游标的问题
  4. 管道命令和xargs的区别(经典解释)
  5. 分治3--黑白棋子的移动
  6. centos7配置jdk1.8环境变量
  7. 细嚼慢咽C++primer(4)——类(1):构造函数,类型别名
  8. 为什么大厂们 一边裁员,一边招人。。
  9. 第四章 自上而下分析
  10. RPC调用和HTTP调用的区别
  11. 迅雷漫画下载工具II beta3 v1.2.3.204
  12. 特别推荐BLOG(一) 程序猿DD的博客
  13. OpenCV中文官方文档-分享
  14. java timer 销毁_java.util.Timer用法须知
  15. Python日常+笔面试
  16. Ubuntu更改用户名
  17. MIMIC数据库权限申请
  18. Java基础教程:k8s快速入门
  19. vue2封装Affix组件实现固定
  20. 测试工程师苦逼时刻,Android 谈谈自动化测试

热门文章

  1. 使用微信开发者工具调试微信网页授权登录-react
  2. vue 项目ztree 异步加载树
  3. android 通话背景音,360 Vizza设置通话背景音的方法
  4. Java树形结构解析
  5. houghpeaks
  6. Unity3D人物换装
  7. Android计步器的实现(2)
  8. 安卓学习之路---计步器算法
  9. ARMv7-M4处理器系列文章-2 编程模型
  10. 教你用PS制作创意分割海报,让海报更有新意