前言:
大家好,我是秋雨清笛,一个在校学生。这两个月里我初步实现了一个简单的RPC框架。做这个RPC框架的主要目的是为了学习,让自己能在平时的CRUD之余学习到一些不一样的东西,了解更多造*过程中的细节。虽然并不是很复杂,但明年秋招的时候希望它能成为我不错的项目经历来对线面试官。
接下来我将以几篇博文来谈谈我是怎么实现它的,遇到了一些什么样的技术细节。大家可以参照以下链接的源码:
https://github.com/PanYuDi/qyqd-rpc
本篇主要内容是RPC简介和定义协议
一、RPC简介
RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
以上是对于RPC的基本解释。我认为RPC只是一种设计的方法而已,它是比网络协议更加高层的一种东西。即便没有RPC我们也可以直接通过Http或TCP与其它系统进行通信。但无论如何通信的本质也就是去调用另外一个系统方法中的一段代码获取返回结果而已。RPC要做的就是屏蔽这其中的细节,让我们不需要关心协议通信的细节直接去调用方法返回结果。底层我们可以用任何协议去实现。
使用RPC的原因如下:
-
可以自定义TCP协议,提高速度
-
高度封装,使用简单
-
用于分布式系统内交互,不需要像http web使用场景那样那么强的通用性,通信速度和开发简易程度才是优先考虑
二、协议的定义
为什么要定义协议?
首先,协议是我们通信的基础,如果服务端不知道客户端发送数据的格式是什么,那么它也不可能去处理这个数据
其次,我们底层采用Netty做TCP的传输(当然我们也可以在其他协议之上构建)。TCP会对数据进行分片,如果不定义协议我们将无法区分每个数据包属于哪个请求,导致一个请求的数据缺少分片或者来自不同请求的分片同时到达,这就是TCP的粘包拆包问题
TCP粘包、拆包的解决
-
通过加入特殊字符的方式区分每一个数据包
-
在协议中定义length字段,优先读取length字段后按照这个大小读取后续的字段
我们采取第二种方式来解决
建一个协议消息实体类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProtocolMessage implements RequestMessage {
/**
* 总长度
*/
int len;
/**
* 请求负载数据类型
*/
ProtocolMessageTypeEnum messageType;
/**
* 用于判断是那个请求
*/
int requestId;
/**
* 实际信息负载
*/
byte[] content;
}
最主要的就是要在协议中定义以上几个字段
-
总长度,用于解决TCP粘包和拆包
-
ProtocolMessageTypeEnum,这里是一个枚举,我们在编码时将它转换为一个byte类型,用于判断请求数据的类型
-
requestId,用于判断是哪个请求。需要这个字段是由于我们底层采用Netty实现,Netty的Reactor模型异步的性质导致我们无法直接同步获取返回结果,因此我们需要这个字段完成同步,后文将详细解释如何实现
-
content,我们实际的传输体内容
实际上我还在协议中多加了几个字段,如下表:
字段名 | 占用位数 | 说明 |
---|---|---|
magic(魔数) | 4B | 固定为qyqd,用于确定是本RPC框架的消息 |
length | 4B | 长度 |
version | 1B | 版本号,防止未来协议升级 |
type | 1B | 消息类型 |
request | 4B | 请求ID |
body | length - 14B | 请求体 |
下一篇我们将用netty实现数据通信