解码器的基类为ByteToMessageDecoder,如果通过继承ByteToMessageDecoder来自定义解码器则需要自己处理“粘包与拆包”的问题,因此一般建议继承更高级的解码器,如下图所示:
LineBasedFrameDecoder是回车换行解码器,如果用户发送的消息以回车换行符作为消息结束的标识,则可以直接使用Netty的LineBasedFrameDecoder对消息进行解码。它的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有“\n”或者“\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。防止由于数据报没有携带换行符导致接收到ByteBuf无限制积压,引起系统内存溢出。
DelimiterBasedFrameDecoder是分隔符解码器,用户可以指定消息结束的分隔符,它可以自动完成以分隔符作为码流结束标识的消息的解码。回车换行解码器实际上是一种特殊的DelimiterBasedFrameDecoder解码器。工作原理同LineBasedFrameDecoder。
FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码。如果是半包消息,FixedLengthFrameDecoder会缓存半包消息并等待下个包到达后进行拼包,直到读取到一个完整的包。
详细使用查看这里。
以Rocketmq的解码器为例进行说明,自定义解码器NettyDecoder继承LengthFieldBasedFrameDecoder。首先利用LengthFieldBasedFrameDecoder解码出一条消息,然后根据自定义协议解析消息。
public class NettyDecoder extends LengthFieldBasedFrameDecoder { private static final Logger log = LoggerFactory.getLogger(RemotingHelper.RemotingLogName); private static final int FRAME_MAX_LENGTH = // Integer.parseInt(System.getProperty("com.rocketmq.remoting.frameMaxLength", "8388608")); public NettyDecoder() { super(FRAME_MAX_LENGTH, 0, 4, 0, 4); } @Override public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { ByteBuf frame = null; try { frame = (ByteBuf) super.decode(ctx, in); if (null == frame) { return null; } ByteBuffer byteBuffer = frame.nioBuffer(); return RemotingCommand.decode(byteBuffer); } catch (Exception e) { log.error("decode exception, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), e); RemotingUtil.closeChannel(ctx.channel()); } finally { if (null != frame) { frame.release(); } } return null; } } public static RemotingCommand decode(final ByteBuffer byteBuffer) { int length = byteBuffer.limit(); int oriHeaderLen = byteBuffer.getInt(); int headerLength = getHeaderLength(oriHeaderLen); byte[] headerData = new byte[headerLength]; byteBuffer.get(headerData); RemotingCommand cmd = headerDecode(headerData, getProtocolType(oriHeaderLen)); int bodyLength = length - 4 - headerLength; byte[] bodyData = null; if (bodyLength > 0) { bodyData = new byte[bodyLength]; byteBuffer.get(bodyData); } cmd.body = bodyData; return cmd; }Netty并没有提供与上面想相匹配的编码器,原因有两点:
解码器主要用于解决TCP底层粘包和拆包;对于编码,就是将POJO对象序列化为ByteBuf,不需要与TCP层面打交道,也就不存在半包编码问题。从应用场景和需要解决的实际问题角度看,双方是非对等的;很难抽象出合适的编码器,对于不同的用户和应用场景,序列化技术不尽相同,在Netty底层统一抽象封装也并不合适;参考:
Netty 系列之 Netty 编解码框架分析