本文主要介绍my-rpc
框架中网络传输数据的格式设计,以及如何在发送和接收数据的过程中进行的编解码过程。
网络传输协议设计
数据在网络中是以字节(二进制)的形式传输的,要让通信的双方都能正确的接收对方发送的信息,一定要定义好数据格式,要让对方知道如何解析,从哪一位开始到哪一位代表什么含义,数据的总长度是多少,以避免多读或少读发生解析错误和粘包等问题。
参考TCP协议对TCP数据包的设计,以下是my-rpc框架设计的网络传输的数据包(my-rpc-package
)格式,分为header
和body
两部分:
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
* +-----+-----+-----+-----+--------+----+----+----+------+-----------+-------+----- --+-----+-----+-------+
header * | magic code |version | full length | messageType| codec|compress| RequestId |
* +-----------------------+--------+---------------------+-----------+-----------+-----------+------------+
* | |
body * | body |
* | |
* | ... ... |
* +-------------------------------------------------------------------------------------------------------+
* 4B magic code(魔法数) 1B version(版本) 4B full length(消息长度) 1B messageType(消息类型)
* 1B codec(序列化类型) 1B compress(压缩类型) 4B requestId(请求的Id)
* body(object类型数据)
- 魔数,4个字节,主要是为了筛选来到服务端的数据包,有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。
- 版本号,1个字节,为版本升级预留
- 消息长度,4个字节,包括header和body的总长度
- 消息类型,1个字节,请求/响应/心跳检测请求/心跳检测响应,根据消息类型来确定body反序列化后的实体类
- 序列化类型,1个字节,body数据的序列化方式(header不序列化)
- 压缩类型,1个字节,body数据的压缩方式(header不压缩)
- requestId,4个字节,标识该次请求的编号
- body,真正传输的数据
网络传输实体设计
RpcMessage.java 是根据my-rpc-package
数据包设计的对应的实体类,是my-rpc
通用的网络传输对象(DTO)
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class RpcMessage {
// 消息类型
private byte messageType;
// 序列化类型 : kryo protostuff
private byte codec;
// 压缩类型 gzip
private byte compress;
// 标识该次消息的编号
private int requestId;
// 具体的传输数据
private Object data;
}
根据消息类型的不同,body主要分为两类,请求RpcRequest和响应RpcResponse。
RpcRequest.java
请求中应该包含远程调用的类名、方法名以及参数等信息。
@Setter
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RpcRequest {
private String requestId; // 该次request的id
private String interfaceName; // 接口全限定名
private String methodName; // 方法名
private Object[] parameters; // 参数
private Class<?>[] paraTypes; // 参数类型
private String group; // 一个接口的多种实现由group区分
public RpcServiceProperties toRpcProperties() {
return RpcServiceProperties.builder().serviceName(this.getInterfaceName()).group(this.getGroup())
.build();
}
}
请求类中还有一个RpcServiceProperties类,该类包含接口名字和group分组两个信息,这两个信息能完全定位一个具体接口实现类,服务消费端也是根据toRpcServiceName()
方法返回值去服务注册中心找到服务提供方的ip地址。
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class