随着微服务、分布式的流行,基本复杂点的项目都会涉及到远程调用,最基础的可以使用http来实现调用,也可以通过一些RPC框架来实现,比如Ailiaba 的dubbo,Thrift等。那么作为一个rpc框架,需要具备哪些基本的元素呢?

  • 网络通讯 在动态代理的实现过程中肯定是需要进行远程调用来获取相应的执行结果的,可以使用Java中的socket或者使用Netty。
  • 序列化和反序列化  数据需要通过网络传输,所以对象都要求可以被序列化和反序列化,常用的有java的Serializable,json(Gson,fastjson等),Protobuf等 这里就直接使用java自带的序列化接口
    不了解序列化的可以参考这篇博客 Java中的序列化和反序列化

当然,如果功能更加完善,一个优秀的rpc框架往往还会具备注册中心,负载均衡等功能,这里就先不做要求了

调用方式 : 在客户端引入服务端接口的jar包,直接调用接口就可以实现远程调用

分析:
因为客户端调用服务端api后,最后需要调用到服务端具体接口的具体方法,那么
1)需要通过动态代理来创建接口的代理类(接口无法实例化直接调用);
2)请求参数里就要求有类信息,方法名,参数集合,这样服务端才能通过反射来调用
3)InvocationHandler的实现类里的invoke具体实现应该是socket调用,这样才能在执行接口方法的时候进行socket通讯

我把这个rpc实现分为两部分,一部分是rpc-server服务端实现,另一部分是rpc-client实现
rpc-server又分为两部分 rpc-server-api(提供jar包给rpc-client调用)和rpc-server-provider

rpc-server-api

/*** 2020/3/7* created by chenpp* 定义接口,将rpc-server-api打包提供给client端使用,* client只要调用对应的接口方法就可以远程调用服务端的具体实现* */
public interface IUserService {public void saveUser(User user);public User getUserById(Integer id);
}
/*** 2020/3/7* created by chenpp* 请求rpc时需要的参数*/
public class RpcRequest implements Serializable {private String className;private String methodName;private Object[] args;public String getClassName() {return className;}public void setClassName(String className) {this.className = className;}...
}

rpc-server-provider

/*** 2020/3/7* created by chenpp* 远程调用的服务端入口,使用socket监听*/
public class RpcServer {private static final ExecutorService executor = Executors.newCachedThreadPool();/*** 注册服务实例,服务注册后,其他客户端通过接口调用就可以调用服务端的实现* */public void register(Object service ,int port)  {ServerSocket serverSocket = null;try {//创建socketserverSocket = new ServerSocket(port);while(true){//监听端口,是个阻塞的方法Socket socket = serverSocket.accept();//处理rpc请求,这里使用线程池来处理executor.submit(new HandleThread(service,socket));}} catch (Exception e) {e.printStackTrace();}finally {if(serverSocket != null){try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}}}
}
package com.chenpp.server;import com.chenpp.request.RpcRequest;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket;/*** 2020/3/7* created by chenpp* 处理RPC请求的线程*/
public class HandleThread implements Runnable {private Socket socket;private Object serviceInstance;public HandleThread(Object serviceInstance, Socket socket) {this.socket  = socket;this.serviceInstance = serviceInstance;}public void run() {ObjectOutputStream oos = null;ObjectInputStream ois = null;try {//从socket中获取RPC请求ois = new ObjectInputStream(socket.getInputStream());RpcRequest rpcRequest = (RpcRequest) ois.readObject();Object result = invoke(rpcRequest);oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(result);oos.flush();} catch (Exception e) {e.printStackTrace();} finally {if (oos != null) {try {ois.close();oos.close();} catch (IOException e) {e.printStackTrace();}}}}private Object invoke(RpcRequest rpcRequest) {String className = rpcRequest.getClassName();Object result = null;try {Class<?> clazz = Class.forName(className);//这里无法实例化,因为传入的是接口类型,接口无法实力哈Object[] parameters = rpcRequest.getArgs();if (parameters == null) {Method method = clazz.getMethod(rpcRequest.getMethodName());result = method.invoke(serviceInstance);} else {Class[] types = new Class[parameters.length];for (int i = 0; i < parameters.length; i++) {types[i] = parameters[i].getClass();}Method method = clazz.getMethod(rpcRequest.getMethodName(), types);result = method.invoke(serviceInstance, parameters);}} catch (Exception e) {e.printStackTrace();}return result;}
}
/*** 2020/3/7* created by chenpp* 启动类,启动rpc服务端*/
public class StartServer {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();RpcServer rpcServer = new RpcServer();rpcServer.register(userService,8080);}
}

rpc-client

public class StartClient {public static void main(String[] args) {//由于rpc-server-api里只有实体类和接口类,想要实例化只能通过代理来实现IUserService userService = RpcProxy.getInstance(IUserService.class,"localhost",8080);User user = new User();user.setAge(12);user.setName("chenpp");userService.saveUser(user);User user1 = userService.getUserById(1);System.out.println(user1.getName()+","+user1.getAge());}
}
/*** 2020/3/7* created by chenpp* 创建代理对象*/
public class RpcProxy<T> {public static <T> T getInstance(Class<T> classInterface, String host, int port) {return (T) Proxy.newProxyInstance(classInterface.getClassLoader(), new Class[]{classInterface}, new RpcInvocationHandler(host, port));}
}
/*** 2020/3/7* created by chenpp*/
public class RpcInvocationHandler implements InvocationHandler {private String host;private int port;public RpcInvocationHandler(String host,int port){this.host = host;this.port = port;}/*** 增强的InvocationHandler,接口调用方法的时候实际是调用socket进行传输*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //将远程调用需要的接口类、方法名、参数信息封装成RPCRequestRpcRequest rpcRequest = new RpcRequest();rpcRequest.setArgs(args);rpcRequest.setClassName(method.getDeclaringClass().getName());rpcRequest.setMethodName(method.getName());//通过socket发送RPCRequest给服务端并获取结果返回Socket socket= new Socket(host,port);ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(rpcRequest);ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());Object result = ois.readObject();return result;}
}

因为接口无法实例化,所以对于每个实现类我都自己手动创建了一个实例对象并发布,可以考虑引入spring来优化对bean的管理,并通过注解来实现服务的发布

/*** 2020/3/7* created by chenpp* 引入Component注解,加了RegisterService注解的类都会被Spring容器管理*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RegisterService {String interfaceClass() ;
}
/*** 实现了InitializingBean接口,那么会在对应的AutoRpcServer实例化之后调用afterPropertiesSet方法* 而实现了ApplicationContextAware接口,当spring容器初始化的时候,会自动的将ApplicationContext注入进来,* 使得当前bean可以获得ApplicationContext上下文* */
@Component
public class AutoRpcServer implements ApplicationContextAware, InitializingBean {private int port;public AutoRpcServer(int port){this.port = port;}private static final ExecutorService executor = Executors.newCachedThreadPool();//key 为对应的接口类名,valeu 为具体的实例private Map<String,Object> map = new HashMap<String, Object>();public void afterPropertiesSet() throws Exception {ServerSocket serverSocket = null;try {//创建socketserverSocket = new ServerSocket(port);while(true){//监听端口,是个阻塞的方法Socket socket = serverSocket.accept();//处理rpc请求,这里使用线程池来处理executor.submit(new HandleThread(map,socket));}} catch (Exception e) {e.printStackTrace();}finally {if(serverSocket != null){try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}}}public void setApplicationContext(ApplicationContext context) throws BeansException {//从spring上下文中获取添加了RegisterService的注解的beanString[] beanNames = context.getBeanNamesForAnnotation(RegisterService.class);for(String beanName : beanNames){Object bean =  context.getBean(beanName);RegisterService annotation = bean.getClass().getAnnotation(RegisterService.class);Class interfaceClass = annotation.interfaceClass();//将接口的类名和对应的实例bean对应起来map.put(interfaceClass.getName(),bean);}}
}
/*** 2020/3/7* created by chenpp* 扫描com.chenpp.impl包下的类,注入spring容器*/
@Component
@ComponentScan("com.chenpp.impl")
public class SpringConfig {//把autoRpcServer bean注入spring容器,初始化完成后会触发InitializingBean接口的afterPropertiesSet调用@Beanpublic AutoRpcServer autoRpcServer(){//这里直接写死启动的端口 : 8080return new AutoRpcServer(8080);}
}
/*** 2020/3/7* created by chenpp* 启动类,通过注解方式启动rpc服务端*/
public class StartServer {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);((AnnotationConfigApplicationContext) applicationContext).start();}
}

GitHub源码地址:https://github.com/dearfulan/cp-rpc

参考:https://www.jianshu.com/p/775d49b30567

手写实现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框架

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

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

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

  5. 面试官:你给我手写个RPC框架看看

    本文主要论述的是"RPC 实现原理",那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote Procedure Call 的缩写,即,远程过程调用.RPC 是一个计算机 ...

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

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

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

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

  8. 05. 手写Spring核心框架

    目录 05 手写Spring核心框架 Pt1 手写IoC/DI Pt1.1 流程设计 Pt1.2 基础配置 application.properties pom.xml web.xml Pt1.3 注 ...

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

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

最新文章

  1. JavaScript编码命名规范及格式规范
  2. 20080826 - Delphi 2009 来了
  3. 成功解决VMware虚拟机中的please remove the installation medium then press enter
  4. Echarts数据可视化grid直角坐标系(xAxis、yAxis)
  5. Flutter搜索框SearchBar
  6. IOC的原理及手动实现
  7. sql基于聚合结果集取最大值_SQL超入门第三篇:写给产品、运营、分析师的SQL教程...
  8. java 装饰流_java装饰流的使用【转】
  9. pythonarp攻击_python通过scapy模块进行arp断网攻击
  10. 关于Git小乌龟的安装及简单使用
  11. 【SPSS统计分析】SPSS19.0简体中文版安装教程(附SPSS19.0简体中文版下载)
  12. 大数据第三季--sqoop(day1)-徐培成-专题视频课程
  13. 以阿尔兹海默症为例:深度解析AI+慢病商业模式
  14. 旅人随笔[02] 量子物理的故事
  15. win10 更新后摄像头问题
  16. 防火墙的基础配置(一)
  17. js 返回页面顶部的实现( layui)
  18. 【SemiDrive源码分析】【X9芯片启动流程】21 - MailBox 核间通信机制介绍(代码分析篇)之 Mailbox for Linux 篇
  19. 苹果手机充电孔变松了_用iPhone 12的MagSafe无线充电器给安卓手机充电?苹果:你想多了|充电器|安卓手机|手机|iphone|iphone手机...
  20. matlab 爱,matlab

热门文章

  1. SpringBoot(配置druid数据源、配置MyBatis、事务控制、druid 监控)
  2. oracle 物化视图、中间表的方案
  3. python如何读入dat数据_python二进制dat数据怎么转成txt文本
  4. FortiGate设置E-mail告警
  5. .NET Core 项目指定SDK版本
  6. Google C++ Coding Style:右值引用(Rvalue Reference)
  7. SqlHelper数据库访问类
  8. vs2008界面查看
  9. HDU多校3 - 6975 Forgiving Matching(多项式匹配字符串)
  10. 2019ICPC(上海) - Color Graph(二分图+状态压缩)