编码和解码的操作其实是一个互逆的过程,把对象转换成什么样的二进制.在吧什么样的二进制转换成对象,如果decode是用json,而encode使用protocolbuf,那这个过程是失败的,如果要实现netty的编码和解码,需要继承这两个接口MessageToByteEncoder
和ByteToMessageDecoder
.这里如果要结合粘包和拆包,就不得不说ByteToMessageDecoder
的另外一个子类LengthFieldBasedFrameDecoder
,这个类可以根据指定的长度来切割byte数组,在调用decode方法,那传进来的butebuf里面的数据刚好就是一个完整的对象的数据.netty在codec包里面也提供了一些通用的decode和encode,如ObjectEncoder
对应ObjectDecoder
,MarshallingEncoder
对应MarshallingDecoder
,这些类的通用实现思想如下.
首先对于encode类,会首先获取到butebuf当前writeIndx,也就是下一个空闲的数组的索引,然后给他写入四个字节的place_holder也就是占位符,接着写进对象编码后的字节,再将writeIndex那个byte,写进整个对象编码后的大小,代码以ObjectEncoder
为例如下:
protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {
//获取到写入的第一个字符的索引
int startIdx = out.writerIndex();
ByteBufOutputStream bout = new ByteBufOutputStream(out);
ObjectOutputStream oout = null;
try {
//写入占位符
bout.write(LENGTH_PLACEHOLDER);
oout = new CompactObjectOutputStream(bout);
//写对象
oout.writeObject(msg);
oout.flush();
} finally {
if (oout != null) {
oout.close();
} else {
bout.close();
}
}
int endIdx = out.writerIndex();
//这边就是计算对象的大小,并把大小写入第一个字符的位置
out.setInt(startIdx, endIdx - startIdx - 4);
}
decode类继承LengthFieldBasedFrameDecoder
,并告诉LengthFieldBasedFrameDecoder
,切割的话字符串大小是多大,并且头一个位置记录的数据是我数据对象的大小,把对象的数据截取出来,调用子类,就可以直接转换成对象了
public class ObjectDecoder extends LengthFieldBasedFrameDecoder {
//需要反序列化的对象的ClassResolver ,里面就一个方法,获取class
private final ClassResolver classResolver;
public ObjectDecoder(ClassResolver classResolver) {
this(1048576, classResolver);
}
public ObjectDecoder(int maxObjectSize, ClassResolver classResolver) {
//第三个参数4是指我place_holder的大小
//最后一个4是指我初始化的时候,头字节截取的大小,因为我们开始的时候没有写其他东西,所以也就是place_holder的大小
super(maxObjectSize, 0, 4, 0, 4);
this.classResolver = classResolver;
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
//调用父类的decode方法,进行拆包
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
//下面就是反序列化
ObjectInputStream ois = new CompactObjectInputStream(new ByteBufInputStream(frame, true), classResolver);
try {
return ois.readObject();
} finally {
ois.close();
}
}
}
这个是通用的一个解决办法,也解决了拆包问题,在将对象转换成byte数组的过程也有实现复杂的方式,比如每写一个数据的属性在前都写入这个属性的大小,解码的时候,在根据长度去截取byte数组,这样实现比较灵活,尤其是对一些比较复杂的对象来说.