java 从零开始手写 RPC (01) 基于 websocket 实现
RPC
解决的问题
RPC 主要是为了解决的两个问题:
解决分布式系统中,服务之间的调用问题。
远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
这一节我们来学习下如何基于 websocket 实现最简单的 rpc 调用,后续会实现基于 netty4 的版本。
完整流程

其中左边的Client,对应的就是前面的Service A,而右边的Server,对应的则是Service B。
下面一步一步详细解释一下。
Service A的应用层代码中,调用了Calculator的一个实现类的add方法,希望执行一个加法运算;
这个Calculator实现类,内部并不是直接实现计算器的加减乘除逻辑,而是通过远程调用Service B的RPC接口,来获取运算结果,因此称之为Stub;
Stub怎么和Service B建立远程通讯呢?这时候就要用到远程通讯工具了,也就是图中的Run-time Library,这个工具将帮你实现远程通讯的功能,比如Java的Socket,就是这样一个库,当然,你也可以用基于Http协议的HttpClient,或者其他通讯工具类,都可以,RPC并没有规定说你要用何种协议进行通讯;
Stub通过调用通讯工具提供的方法,和Service B建立起了通讯,然后将请求数据发给Service B。需要注意的是,由于底层的网络通讯是基于二进制格式的,因此这里Stub传给通讯工具类的数据也必须是二进制,比如calculator.add(1,2),你必须把参数值1和2放到一个Request对象里头(这个Request对象当然不只这些信息,还包括要调用哪个服务的哪个RPC接口等其他信息),然后序列化为二进制,再传给通讯工具类,这一点也将在下面的代码实现中体现;
二进制的数据传到Service B这一边了,Service B当然也有自己的通讯工具,通过这个通讯工具接收二进制的请求;
既然数据是二进制的,那么自然要进行反序列化了,将二进制的数据反序列化为请求对象,然后将这个请求对象交给Service B的Stub处理;
和之前的Service A的Stub一样,这里的Stub也同样是个“假玩意”,它所负责的,只是去解析请求对象,知道调用方要调的是哪个RPC接口,传进来的参数又是什么,然后再把这些参数传给对应的RPC接口,也就是Calculator的实际实现类去执行。很明显,如果是Java,那这里肯定用到了反射。
RPC接口执行完毕,返回执行结果,现在轮到Service B要把数据发给Service A了,怎么发?一样的道理,一样的流程,只是现在Service B变成了Client,Service A变成了Server而已:Service B反序列化执行结果->传输给Service A->Service A反序列化执行结果 -> 将结果返回给Application,完毕。
简单实现
假设服务 A,想调用服务 B 的一个方法。
因为不在同一个内存中,无法直接使用。如何可以实现类似 Dubbo 的功能呢?
这里不需要使用 HTTP 级别的通信,使用 TCP 协议即可。
common
公用模块,定义通用对象。
- Rpc 常量
public interface RpcConstant {
/**
* 地址
*/
String ADDRESS = "127.0.0.1";
/**
* 端口号
*/
int PORT = 12345;
}
- 请求入参
public class RpcCalculateRequest implements Serializable {
private static final long serialVersionUID = 6420751004355300996L;
/**
* 参数一
*/
private int one;
/**
* 参数二
*/
private int two;
//getter & setter & toString()
}
- 服务接口
public interface Calculator {
/**
* 计算加法
* @param one 参数一
* @param two 参数二
* @return 返回结果
*/
int add(int one, int two);
}
server
- 服务接口的实现
public class CalculatorImpl implements Calculator {
@Override
public int add(int one, int two) {
return one + two;
}
}
- 启动服务
public static void main(String[] args) throws IOException {
Calculator calculator = new CalculatorImpl();
try (ServerSocket listener = new ServerSocket(RpcConstant.PORT)) {
System.out.println("Server 端启动:" + RpcConstant.ADDRESS + ":" + RpcConstant.PORT);
while (true) {
try (Socket socket = listener.accept()) {
// 将请求反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object object = objectInputStream.readObject();
System.out.println("Request is: " + object);
// 调用服务
int result = 0;
if (object instanceof RpcCalculateRequest) {
RpcCalculateRequest calculateRpcRequest = (RpcCalculateRequest) object;
result = calculator.add(calculateRpcRequest.getOne(), calculateRpcRequest.getTwo());
}
// 返回结果
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
启动日志:
Server 端启动:127.0.0.1:12345
client
- 客户端调用
public static void main(String[] args) {
Calculator calculator = new CalculatorProxy();
int result = calculator.add(1, 2);
System.out.println(result);
}
- 计算的代理类
public class CalculatorProxy implements Calculator {
@Override
public int add(int one, int two) {
try {
Socket socket = new Socket(RpcConstant.ADDRESS, RpcConstant.PORT);
// 将请求序列化
RpcCalculateRequest calculateRpcRequest = new RpcCalculateRequest(one, two);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
// 将请求发给服务提供方
objectOutputStream.writeObject(calculateRpcRequest);
// 将响应体反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
Object response = objectInputStream.readObject();
if (response instanceof Integer) {
return (Integer) response;
} else {
throw new RuntimeException();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
- 调用日志
client 端
3
server 端
Server 端启动:127.0.0.1:12345
Request is: RpcCalculateRequest{one=1, two=2}
开源地址
为了便于大家学习,以上源码已经开源:
我是老马,期待与你的下次重逢。

java 从零开始手写 RPC (01) 基于 websocket 实现的更多相关文章
- 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框架指北另送贴心注释代码一套
Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...
- 看了这篇你就会手写RPC框架了
一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...
- 手写RPC框架(六)整合Netty
手写RPC框架(六)整合Netty Netty简介: Netty是一个基于NIO的,提供异步,事件驱动的网络应用工具,具有高性能高可靠性等特点. 使用传统的Socket来进行网络通信,服务端每一个连接 ...
- 从零开始手写 dubbo rpc 框架
rpc rpc 是基于 netty 实现的 java rpc 框架,类似于 dubbo. 主要用于个人学习,由渐入深,理解 rpc 的底层实现原理. 前言 工作至今,接触 rpc 框架已经有很长时间. ...
- OpenCV手写数字字符识别(基于k近邻算法)
摘要 本程序主要参照论文,<基于OpenCV的脱机手写字符识别技术>实现了,对于手写阿拉伯数字的识别工作.识别工作分为三大步骤:预处理,特征提取,分类识别.预处理过程主要找到图像的ROI部 ...
随机推荐
- CentOS8安装jdk1.8
安装方法 CentOS8上使用 yum 直接安装,环境变量自动配置好 查看是否已安装 看到下面结果,说明已经安装配置 jdk [root@iZ2ze8crquorxf6c7l0eluZ ~]# jav ...
- 等待唤醒机制----线程池----lambda表达式
1.等待唤醒机制 1.1线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同. 比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的 ...
- 刷题-力扣-LCP 07. 传递信息
LCP 07. 传递信息 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/chuan-di-xin-xi 著作权归领扣网络所有.商业转 ...
- Spring事物入门简介及AOP陷阱分析
转载请注明出处: https://www.cnblogs.com/qnlcy/p/15237377.html 一.事务的定义 事务(Transaction),是指访问并可能更新数据库中各种数据项的一个 ...
- 《Go语言圣经》阅读笔记:第三章基础数据类型
第三章 基础数据类型 Go语言将数据类型分为四类: 基础类型 数字 整数 浮点数 复数 字符串 布尔 复合类型 数据 结构体 引用类型 指针 切片 字典 函数 通道 接口类型 在此章节中先介绍基础类型 ...
- js 显示日期时间,时间过一秒加1
html: <div id="data"><font>2017年10月17日 15:11:11</font></span> js: ...
- python库--pandas--文本文件读取
.read_table() / read_csv() filepath_or_buffer 文件路径 sep='\t' 分隔符. 设置为N, 将尝试自动确定 delimiter=N sep的备 ...
- 机器学习——softmax回归
softmax回归 前面介绍了线性回归模型适用于输出为连续值的情景.在另一类情景中,模型输出可以是一个像图像类别这样的离散值.对于这样的离散值预测问题,我们可以使用诸如 softmax 回归在内的分类 ...
- cnblogs-theme-silence 主题设置简约风格
本文参考 更改博客皮肤 更改博客皮肤为Custom 页面定制CSS代码 勾选禁用模板默认CSS 导入复制该文件内容到代码框中 配置代码块复制功能 样式 和 右侧滑动条样式 /*复制功能添加按钮 beg ...
- PHP中命名空间是怎样的存在?(三)
这是与命名空间有关的最后一篇.最后还是两个比较简单的内容,是关于命名空间和全局相关的一些类.函数.常量的使用对比.当然,最后我们还会总结一下命名空间的名称解析规则做为这三篇系列文章的结束. 全局空间 ...