NP-18-自定义协议

在当今比较流行的水平拆分的架构之下,RPC协议很是流行,这样可以使各个项目解耦,使得更加灵活,每个项目之间通过远程调用交互,相互之间定义一个通讯私有协议,然后解析,这样就可以进行数据接口交互。

协议类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.fuyi.netty.custom;

public class CustomMsg {

/**
* 类型 系统编号 0xAB 表示A系统,0xBC 表示B系统
*/
private byte type;

/**
* 信息标志 0xAB 表示心跳包 0xBC 表示超时包 0xCD 业务信息包
*/
private byte flag;

/**
* 主题信息的长度
*/
private int length;

/**
* 主题信息
*/
private String body;

public CustomMsg() {}

public CustomMsg(byte type, byte flag, int length, String body) {
super();
this.type = type;
this.flag = flag;
this.length = length;
this.body = body;
}

// getter/setter省略
}

我们规定两个系统通过Netty去发送这样的一个格式的信息,CustomMsg中包含这样的几类信息:

  1. type表示发送端的系统类型
  2. flag表示发送信息的类型,是业务数据,还是心跳包数据
  3. length表示主题body的长度
  4. body表示主题信息

有了这样的相互规定,发送端与接收端按照这种格式去编码和解码数据,这样就很容易的进行数据交互,当然如果netty不提供任何的类,我们也能进行编码解码,但是Netty还是提供了一个现有的类,这样可以避免我们重复造车,并且即使我们愿意重复造车,我们造的车也不一定比Netty好,所以我们还是直接使用吧

自定义LengthFieldBasedFrameDecoder

Netty提供的类叫做LengthFieldBasedFrameDecoder,与其他的解码器不一致的地方是它需要几个参数作为它的构造函数参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.fuyi.netty.custom;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

public class CustomDecoder extends LengthFieldBasedFrameDecoder {

/**
* 判断传送客户端传送过来的数据是否按照协议传输,头部信息的大小应该是 byte+byte+int = 1+1+4 = 6
*/
private static final int HEADER_SIZE = 6;

/**
*
* @param maxFrameLength 解码时,处理每个帧数据的最大长度
* @param lengthFieldOffset 该帧数据中,存放该帧数据的长度的数据的起始位置
* @param lengthFieldLength 记录该帧数据长度的字段本身的长度
* @param lengthAdjustment 修改帧数据长度字段中定义的值,可以为负数
* @param initialBytesToStrip 解析的时候需要跳过的字节数
* @param failFast 为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常
*/
public CustomDecoder(int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip, boolean failFast) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
}

@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if(in == null) {
return null;
}

if(in.readableBytes() < HEADER_SIZE) {
throw new Exception("可读信息比头部信息还少");
}

byte type = in.readByte();
byte flag = in.readByte();
int length = in.readInt();

if(in.readableBytes() < length) {
throw new Exception("头部中说body信息有" + length + ", 但实际没这么多。。。");
}

ByteBuf buf = in.readBytes(length); // 读取in中的length长度数据,转存到buf中
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req); // 将buf中的数据读到req中
String body = new String(req, "UTF-8");

CustomMsg customMsg = new CustomMsg(type, flag, length, body);
return customMsg;
}
}

头部信息的大小我们这边写的是6,原因在代码里面也解释了,byte是一个字节,int是四个字节,那么头部大小就是6个字节,接下来就是要定义构造函数了,构造函数的入参的解释代码里已经标注了,我们真实的入参是:

1
2
3
4
5
private static final int maxFrameLength = 1024 * 1024;
private static final int lengthFieldOffset = 2;
private static final int lengthFieldLength = 4;
private static final int lengthAdjustment = 0;
private static final int initialBytesToStrip = 0;
  1. LENGTH_FIELD_LENGTH指的就是我们这边CustomMsg中length这个属性的大小,我们这边是int型,所以是4
  2. LENGTH_FIELD_OFFSET指的就是我们这边length字段的起始位置,因为前面有type和flag两个属性,且这两个属性都是byte,两个就是2字节,所以偏移量是2
  3. LENGTH_ADJUSTMENT指的是length这个属性的值,假如我们的body长度是40,有时候,有些人喜欢将length写成44,因为length本身还占有4个字节,这样就需要调整一下,那么就需要-4,我们这边没有这样做,所以写0就可以了