手写RPC-简陋版
前言
最近不小心被隔离,放假思考一番,决定开始在手写序列。这个序列在之前看Nacous和网关源码的时候就有想法,只是一直没落实下来,趁着隔离行动起来。
必备知识介绍
序列化与反序列化
序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化;
反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化;
在Java中通过 JDK 提供了 Java 对象的序列化方式实现对象序列化传输,主要通过输出流java.io.ObjectOutputStream和对象输入流java.io.ObjectInputStream来实现;
java.io.ObjectOutputStream:表示对象输出流 , 它的 writeObject(Object obj)方法可以对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中;
java.io.ObjectInputStream:表示对象输入流 ,它的 readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;
需要注意的是,被序列化的对象需要实现 java.io.Serializable 接口。Java 的序列化机制是通过判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是 InvalidCastException。
另外一个需要注意的就是transient关键字,被transient修饰的属性不会被序列化,如果从重写writeobject和readobject则可以重新被序列化。在JDK中的案例就是ArryList中修饰Object[]的数组使用transient关节字,保证传输过程中不照成浪费,只传输有用的值。本质是是通过反射来实现调用writeobject和readobject。
什么是Socket通信
Socket 的原意是“插座”,在计算机通信领域,Socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 Socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

RPC原理介绍
RPC是什么
所谓的RPC其实是为了不同主机的两个进程间通信而产生的,通常不同的主机之间的进程通信,程序编写需要考虑到网络通信的功能,这样程序的编写将会变得复杂。RPC就来解决这一问题的,一台主机上的进程对另外一台主机的进程发起请求时,内核会将请求转交给RPC client,RPC client经过报文的封装转交给目标主机的RPC server,RPC server就将报文进行解析,还原成正常的请求,转交给目标主机上的目标进程。在我们看来在就像是在同一台主机上的两个进程通信一样,完全没有意识到是在不同的主机上。因此RPC其实也可以看做是一种协议或者是编程框架,目的是为了简化分布式程序的编写。
RPC基本流程

Rpc Client通过传入的IP、端口号、调用类以及方法的参数,通过动态代理找到具体的调用类的方法,将请求的类、方法序列化,传输到服务端;
当Rpc Service收到请求以后,将传入类和方法反序列化,通过反射找到对应的类的方法进行调用,最后将返回结果进行序列化,返回客户端;
Rpc Client收到返回值以后,进行反序列化,最后将结果展示;
手撸RPC
从RPC的基本流程可以看到,对于RPC性能来说可以提升的主要两个地方分别是序列化工具以及通信框架,在我们整个手撸系列里面会一步一步将其中的组件提升为高性能的组件,从阻塞IO到NIO,从JDK原始序列化框架到现在五花把门序列化框架,从手动的创建对象到Spring自动化创建对象,注册中心引入等等,整个过程还会伴随知识介绍,让我们一起携手共进。
迈出第一步
第一步我们只做到支持一个类的远程调用,采用JDK携带的序列化和反序列的工具以及阻塞连接的方式。

整体项目结构分为三部分,rpc-api作为Api提供,rpc-common主要是提供公共封装供client和service调用,rpc-v1包括rpc-v1-client主要是客户端调用封装,rpc-v1-service作为Api实现以及暴露对应方法,以后每次做的更改我都会新增一个版本,这样会方便新手进行学习。
Service端

服务端的实现采用ServerSocket监听某个端口,循环接收连接请求,如果发来了请求就创建一个线程,在新线程中处理调用,核心类就是RpcProxyService和ProcessorHandler,实现如下:
RpcProxyService
@Slf4j
public class RpcProxyService {
private ExecutorService threadPool;
public RpcProxyService() {
int corePoolSize = 5;
int maximumPoolSize = 200;
long keepAliveTime = 60;
BlockingQueue<Runnable> workingQueue = new ArrayBlockingQueue<>(100);
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("socket-pool-").build();
threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workingQueue, threadFactory);
}
/**
* 暴露方法,在注册完成以后服务后立刻开始监听
*
* @param service
* @param port
*/
public void register(Object service, int port) {
try (ServerSocket serverSocket = new ServerSocket(port);) {
Socket socket;
while ((socket = serverSocket.accept()) != null) {
log.info("客户端连接IP为:" + socket.getInetAddress());
threadPool.execute(new ProcessorHandler(socket, service));
}
} catch (IOException e) {
log.error("连接异常", e);
}
}
}
ProcessorHandler
@Slf4j
public class ProcessorHandler implements Runnable {
private Socket socket;
private Object service;
public ProcessorHandler(Socket socket, Object service) {
this.service = service;
this.socket = socket;
}
@Override
public void run() {
try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
//从输入流读取参数
RpcRequest rpcRequest = (RpcRequest) inputStream.readObject();
//通过反射获取到方法
Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
//执行方法
Object result = method.invoke(service, rpcRequest.getParameters());
outputStream.writeObject(RpcResponse.ok(result));
outputStream.flush();
} catch (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) {
//此处可再次进行包装将异常情况分类
log.error("调用时发生错误", exception);
}
}
}
Client端

Client端通过RpcClientProxy动态代理(采用JDK动态代理)生成代理对象,然后通过执行RemoteInvocationHandler的invoke来确定调用具体的类和方法,也就是构建RpcRequest对象,最后通过RpcClient发起远程调用。
RpcClientProxy
public class RpcClientProxy {
public <T> T getProxy(Class<T> interfaceClass, String host, int port) {
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new RemoteInvocationHandler(host, port));
}
}
RemoteInvocationHandler
public class RemoteInvocationHandler implements InvocationHandler {
private String host;
private int port;
public RemoteInvocationHandler(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//构造请求参数
RpcRequest rpcRequest = RpcRequest.builder()
.interfaceName(method.getDeclaringClass().getName())
.methodName(method.getName())
.parameters(args)
.paramTypes(method.getParameterTypes())
.build();
//发送请求
RpcClient rpcClient = new RpcClient();
return ((RpcResponse) rpcClient.send(rpcRequest, host, port)).getData();
}
}
RpcClient
@Slf4j
public class RpcClient {
public Object send(RpcRequest rpcRequest, String host, int port) {
try (Socket socket = new Socket(host, port)) {
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
//序列化
outputStream.writeObject(rpcRequest);
outputStream.flush();
return inputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
log.error("调用时发生异常", e);
return null;
}
}
}
Common端
Common端目前做入参和出参封装,代码如下:
RpcRequest
@Data
@Builder
public class RpcRequest implements Serializable {
/**
* 接口名称
*/
private String interfaceName;
/**
* 方法名称
*/
private String methodName;
/**
* 参数
*/
private Object[] parameters;
/**
* 参数类型
*/
private Class<?>[] paramTypes;
}
RpcResponse
@Data
public class RpcResponse<T> implements Serializable {
/**
* 状态码
*/
private Integer code;
/**
* 提醒信息
*/
private String message;
/**
* 返回信息
*/
private T data;
public static <T> RpcResponse<T> ok(T data) {
RpcResponse<T> rpcResponse = new RpcResponse<>();
rpcResponse.setCode(ResponseCode.SUCCESS.getCode());
rpcResponse.setData(data);
rpcResponse.setMessage(rpcResponse.getMessage());
return rpcResponse;
}
public static <T> RpcResponse<T> error(int code, String message) {
RpcResponse<T> rpcResponse = new RpcResponse<>();
rpcResponse.setCode(code);
rpcResponse.setMessage(message);
return rpcResponse;
}
}
整体代码我已经上传github,对于初学者一定要联调一下,理解清楚整体的RPC流程。
欢迎大家点点关注,点点赞!

手写RPC-简陋版的更多相关文章
- 手写RPC框架指北另送贴心注释代码一套
Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...
- 看了这篇你就会手写RPC框架了
一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...
- java 从零开始手写 RPC (03) 如何实现客户端调用服务端?
说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...
- java 从零开始手写 RPC (04) -序列化
序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...
- java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端
通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...
- java 从零开始手写 RPC (07)-timeout 超时处理
<过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...
- 手写RPC框架(六)整合Netty
手写RPC框架(六)整合Netty Netty简介: Netty是一个基于NIO的,提供异步,事件驱动的网络应用工具,具有高性能高可靠性等特点. 使用传统的Socket来进行网络通信,服务端每一个连接 ...
- 识别手写数字增强版100% - pytorch从入门到入道(一)
手写数字识别,神经网络领域的“hello world”例子,通过pytorch一步步构建,通过训练与调整,达到“100%”准确率 1.快速开始 1.1 定义神经网络类,继承torch.nn.Modul ...
- 来,我们手写一个简易版的mock.js吧(模拟fetch && Ajax请求)
预期的mock的使用方式 首先我们从使用的角度出发,思考编码过程 M1. 通过配置文件配置url和response M2. 自动检测环境为开发环境时启动Mock.js M3. mock代码能直接覆盖g ...
随机推荐
- SpringBoot(1):初始SpringBoot
一. SpringBoot 简介 1. SpringBoot介绍 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程.该框架使用了特 ...
- 3.0 go mod之远程仓库搭建-代码示例
注意事项 所谓的远程仓库指的是github,个人首次使用go mod在其他云仓库上尝试,并未成功,这浪费了我近2小时的时间: 如果你是初次尝试,那么除了github的地址换一下之外,其他的都按照示例操 ...
- 图书管理系统总结——数据库操纵(二):DML语句
这里以最基本的DML语句作为例子,其他各种复杂的DML语句可以根据这些基本JDBC语句得到. 一.查询表格 这里以两张表关联查询为例,采用动态方式,根据输入的条件在WHERE语句后面接上相应的各种条件 ...
- Appium获取toast消息遇到的问题(一)
一.运行错误 Android获取toast,需要在参数里设置automationName:Uiautomator2 1 # 设置设备的信息 2 desired_caps = { 3 'platform ...
- 【HarmonyOS】【DevEco Studio】NOTE04:How to Jump to a Page(页面间的跳转)
页面创建与基本设置 创建页面 创建两个新页面,分别为AbilityPage1.AbilityPage2 设置页面基本内容 以AbilityPage1为例 导包 import com.example.m ...
- 赋能开发:捷码携手达内教育打造IT职业教育新生态
近日,达内教育与远眺科技签约联合培养的第一批低代码开发方向的高职学生,在杭州未来科技城捷码总部顺利毕业,首期合格学员总数超过30名.随着这些接受了"捷码"低代码平台全程" ...
- 2021 中国.NET开发者峰会近50场热点技术专题揭秘
01 大会介绍 .NET Conf China 2021 是面向开发人员的社区峰会,基于 .NET Conf 2021的活动,庆祝 .NET 6 的发布和回顾过去一年来 .NET 在中国的发展成果展 ...
- tableau绘制热力地图
一.右键国家地区和城市字段分别设置为地理角色-国家地区和城市 二.双击国家地区和城市添加到工作表 三.把订单id拖拽至标记卡的详细信息,标记改为密度显示,颜色设置为温度发散 四.最终整理结果如下图所示
- AOP面向切面
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点 ...
- [BUUCTF]PWN——others_shellcode
others_shellcode 附件 解题步骤: 例行检查,32位程序,开启了NX(堆栈不可执行)和PIE(地址随机化)双重保护 试运行了一下,发现直接就能执行shell的命令 远程连接运行一下,直 ...