自定义协议?
第一次听到这个名词的时候,感觉好高大上!后面学习了这块类容以后,发现也就那样,所谓的自定义协议,就是自己定义一套数据传输的规则
。这么说你不一定明白
我们知道只有二进制才能在网络中传输,所以 RPC 请求在发送到网络中之前,他需要把方法调用的请求参数转成二进制但,在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包(合并的前提是同一
个 TCP 连接上的数据),至于怎么拆分合并,这其中的细节会涉及到系统参数配置和 TCP窗口大小。对于服务提供方应用来说,他会从 TCP 通道里面收到很多的二进制数据,那这时候怎么识别出哪些二进制是第一个请求的呢?因此,自定义协议的第一个必不可少的元素来啦。
消息长度
标识消息的长度,服务端收到消息时先读取固定位置的数据获取消息长度,然后就知道这条消息应该从哪开始读,读多少,最简单的消息协议如下
但是仅仅这些还不够,为什么呢?
因为前面我们提到了,我们的可能是支持多种序列化方式的,如果协议里没有序列化方式的标识,我们即使拿到数据也没办法正确的反序列化,因此协议头里面还应该加上序列化方式的标识。
既然是自定义的协议,我们是不是应该有个固定标识这条消息是按我们的协议走的,就像看一个文件的类型不应该只是看文件的后缀名一样。不同类型的文件我们会在文件的开头以一个固定的自负或者数字来表示,这个标识有一个统一的称呼魔数
。
你以为一个自定义协议就长这样?
好的协议不但要考虑现在,还要考虑将来!
我们的协议还需要一个协议版本,不同的时候我们可能会按照不同的方式来编码消息。为了保证协议的可扩展性,我们还可以定义一个协议头的长度,方便以后我们协议头部分内容的扩展等等。写代码不难,要想写一份好代码却不是一件简单的事。
本着能简就减的原则,我们的自定义协议长这样
说了这么多,有的人可能要问了,http协议不够好么?你认为自己能写的比http协议更好?
- 相对于 HTTP 的用处,RPC 更多的是负责应用间的通信,所以
性能要求相对更高。 - HTTP 协议的数据包大小相对请求数据本身要大很多,又需要加入
很多无用的内容,比如换行符号、回车符等。 - 还有一个更重要的原因是,HTTP 协议属于无状态协议,客户端无法对请求和响应进行关联,每次请求都需要重新建立连接,响应完成后再关闭连接。
说了这么多,我们可以把这个自定义协议实现出来了,一个完整自定义协议里面包含了协议头和消息体,因此由协议头和消息内容两部分共同组成自定义协议。
package com.info.protocol.netty.core;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Header {
/*
+-------------------------------------------------------------------------------------------+
|魔数 16bit|协议版本 8bit|序列化方式 8bit|协议头长度 16bit| 消息长度 32bit |消息类型(请求还是响应)2bit|
+-------------------------------------------------------------------------------------------+
*/
private short magic;
private byte protocolVersion;
private byte serializeType;
private short protocolHeaderLength;
private int messageLength;
private byte messageType;
}
package com.info.protocol.netty.core;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Protocol<T> {
private Header header;
private T content;
}
定义一个枚举方便管理消息类型
package com.info.protocol.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum MessageTypeEnum {
REQUEST((byte) 1),
RESPONSE((byte) 2),
heart_beat((byte) 3);
private byte code;
public static MessageTypeEnum getMessageTypeEnumByCode(int code) {
return Arrays.stream(MessageTypeEnum.values())
.filter(e -> e.getCode() == code)
.findFirst()
.orElseGet(null);
}
}
本节的内容就先到这里了,欲知后事如何,且听下回(拍醒木)分解!
系列文章传送门如下:
手写RPC(一) 絮絮叨叨
手写RPC(二) 碎碎念
手写RPC(三) 基础结构搭建
手写RPC(四) 核心模块网络协议模块编写 ---- netty服务端
手写RPC(六) 核心模块网络协议模块编写 ---- 实现编解码器
手写RPC(七) 核心模块网络协议模块编写 ---- 实现客户端
手写RPC(八) provider、consumer 实现
手写RPC(九) 测试
手写RPC(十) 优化
关于 LengthFieldBasedFrameDecoder 不得不说的事