简介
在之前的系列文章中,咱们提到了netty中的channel只承受ByteBuf类型的对象,如果不是ByteBuf对象的话,须要用编码和解码器对其进行转换,明天来聊一下netty自定义的编码和解码器实现中须要留神的问题。
自定义编码器和解码器的实现
在介绍netty自带的编码器和解码器之前,通知大家怎么实现自定义的编码器和解码器。
netty中所有的编码器和解码器都是从ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter衍生而来的。
对于ChannelOutboundHandlerAdapter来说,最重要的两个类是MessageToByteEncoder 和 MessageToMessageEncoder 。
MessageToByteEncoder是将音讯编码成为ByteBuf,这个类也是咱们自定义编码最罕用的类,间接继承这个类并实现encode办法即可。留神到这个类有一个泛型,这个泛型指定的就是音讯的对象类型。
例如咱们想将Integer转换成为ByteBuf,能够这样写:
public class IntegerEncoder extends MessageToByteEncoder<Integer> { @Override public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception { out.writeInt(msg); } }
MessageToMessageEncoder是在音讯和音讯之间进行转换,因为音讯并不能间接写入到channel中,所以须要和MessageToByteEncoder配合应用。
上面是一个Integer到String的例子:
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> { @Override public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out) throws Exception { out.add(message.toString()); } }
对于ChannelInboundHandlerAdapter来说,最重要的两个类是ByteToMessageDecoder和MessageToMessageDecoder 。
ByteToMessageDecoder是将ByteBuf转换成对应的音讯类型,咱们须要继承这个类,并实现decode办法,上面是一个从ByteBuf中读取所有可读的字节,并将后果放到一个新的ByteBuf中,
public class SquareDecoder extends ByteToMessageDecoder { @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { out.add(in.readBytes(in.readableBytes())); } }
MessageToMessageDecoder是音讯和音讯之间的转换,同样的只须要实现decode办法即可,如下从String转换到Integer:
public class StringToIntegerDecoder extends MessageToMessageDecoder<String> { @Override public void decode(ChannelHandlerContext ctx, String message, List<Object> out) throws Exception { out.add(message.length()); } }
ReplayingDecoder
下面的代码看起来很简略,然而在实现的过程中还有一些问题要留神。
对于Decoder来说,咱们从ByteBuf中读取数据,而后进行转换。然而在读取的过程中,并不知道ByteBuf中数据的变动状况,有可能在读取的过程中ByteBuf还没有筹备好,那么就须要在读取的时候对ByteBuf中可读字节的大小进行判断。
比方咱们须要解析一个数据结构,这个数据结构的前4个字节是一个int,示意前面byte数组的长度,咱们须要先判断ByteBuf中是否有4个字节,而后读取这4个字节作为Byte数组的长度,而后再读取这个长度的Byte数组,最终失去要读取的后果,如果其中的某一步呈现问题,或者说可读的字节长度不够,那么就须要间接返回,期待下一次的读取。如下所示:
public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception { if (buf.readableBytes() < 4) { return; } buf.markReaderIndex(); int length = buf.readInt(); if (buf.readableBytes() < length) { buf.resetReaderIndex(); return; } out.add(buf.readBytes(length)); } }
这种判断是比较复杂同时也是可能出错的,为了解决这个问题,netty提供了 ReplayingDecoder用来简化下面的操作,在ReplayingDecoder中,假如所有的ByteBuf曾经处于筹备好的状态,间接从两头读取即可。
下面的例子用ReplayingDecoder重写如下:
public class IntegerHeaderFrameDecoder extends ReplayingDecoder<Void> { protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception { out.add(buf.readBytes(buf.readInt())); } }
它的实现原理是去尝试读取对应的字节信息,如果没有读到,则抛出异样,ReplayingDecoder接管到异样之后,会从新调用decode办法。
尽管ReplayingDecoder应用起来非常简单,然而它有两个问题。
第一个问题是性能问题,因为会去反复调用decode办法,如果ByteBuf自身并没有变动,就会导致反复decode同一个ByteBuf,照成性能的节约。解决这个问题就是在在decode的过程中分阶段进行,比方下面的例子中,咱们须要先读取Byte数组的长度,而后再读取真正的byte数组。所以在读完byte数组长度之和,能够调用checkpoint()办法做一个保留点,下次再执行decode办法的时候就能够跳过这个保留点,持续后续的执行过程,如下所示:
public enum MyDecoderState { READ_LENGTH, READ_CONTENT; } public class IntegerHeaderFrameDecoder extends ReplayingDecoder<MyDecoderState> { private int length; public IntegerHeaderFrameDecoder() { // Set the initial state. super(MyDecoderState.READ_LENGTH); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception { switch (state()) { case READ_LENGTH: length = buf.readInt(); checkpoint(MyDecoderState.READ_CONTENT); case READ_CONTENT: ByteBuf frame = buf.readBytes(length); checkpoint(MyDecoderState.READ_LENGTH); out.add(frame); break; default: throw new Error("Shouldn't reach here."); } } }
第二个问题是同一个实例的decode办法可能会被调用屡次,如果咱们在ReplayingDecoder中有公有变量的话,则须要思考对这个公有变量的荡涤工作,防止屡次调用造成的数据净化。
总结
通过继承下面的几个类,咱们就能够本人实现编码和解码的逻辑了。然而如同还有点问题,自定义编码和解码器是不是太简单了?还须要判断要读取的byte数组的大小。有没有更加简略的办法呢?
有的,敬请期待netty系列的下一篇文章:netty自带的编码器和解码器.
本文的例子能够参考:learn-netty4
本文已收录于 http://www.flydean.com/14-netty-cust-codec/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!