Netty5的CodeC编解码对以往的版本进行了简化,没有单独的Encoder / Decoder接口,都继承了ChannelHandlerApdater类,来实现ChannelHandler接口。
对Decoder来说,主要有两个顶层的抽象类,一个是从字节流到消息的ByteToMessageDecoder,一个是中间消息到业务消息的MessageToMessageDecoder。
ByteToMessageDecoder放置在MessageToMessageDecoder前面,处理inbound事件。
拿读IO数据来举例,最初的数据来源与底层的SocketChannel的读方法,比如UnpooledDirectByteBuf,先分配一个直接内存缓冲区DirectByteBuffer,然后把数据复制到ByteBuf,返回ByteBuf给Netty上层的类。
对UnpooledDirectByteBuf来说,它底层封装了Java的ByteBuffer,使用ByteBuf来对ByteBuffer进行操作,并发ByteBuf抛给顶层
// NioSocketChannel.doReadBytes protected int doReadBytes(ByteBuf byteBuf) throws Exception { return byteBuf.writeBytes(javaChannel(), byteBuf.writableBytes()); } // AbstractByteBuf.writeBytes public int writeBytes(ScatteringByteChannel in, int length) throws IOException { ensureWritable(length); int writtenBytes = setBytes(writerIndex, in, length); if (writtenBytes > 0) { writerIndex += writtenBytes; } return writtenBytes; } //UnpooledDirectByteBuf.setBytes public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { ensureAccessible(); ByteBuffer tmpBuf = internalNioBuffer(); tmpBuf.clear().position(index).limit(index + length); try { return in.read(tmpNioBuf); } catch (ClosedChannelException e) { return -1; } }
1. 固定长度来分帧
2.根据制定的分隔符
3. 采用消息头+消息体的方式,消息头制定消息长度
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof ByteBuf) { RecyclableArrayList out = RecyclableArrayList.newInstance(); try { ByteBuf data = (ByteBuf) msg; first = cumulation == null; if (first) { cumulation = data; } else { if (cumulation.writerIndex() > cumulation.maxCapacity() - data.readableBytes()) { expandCumulation(ctx, data.readableBytes()); } cumulation.writeBytes(data); data.release(); } callDecode(ctx, cumulation, out); } catch (DecoderException e) { throw e; } catch (Throwable t) { throw new DecoderException(t); } finally { if (cumulation != null && !cumulation.isReadable()) { cumulation.release(); cumulation = null; } int size = out.size(); decodeWasNull = size == 0; for (int i = 0; i < size; i ++) { ctx.fireChannelRead(out.get(i)); } out.recycle(); } } else { ctx.fireChannelRead(msg); } } protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { try { while (in.isReadable()) { int outSize = out.size(); int oldInputLength = in.readableBytes(); decode(ctx, in, out); // Check if this handler was removed before continuing the loop. // If it was removed, it is not safe to continue to operate on the buffer. // // See https://github.com/netty/netty/issues/1664 if (ctx.isRemoved()) { break; } if (outSize == out.size()) { if (oldInputLength == in.readableBytes()) { break; } else { continue; } } if (oldInputLength == in.readableBytes()) { throw new DecoderException( StringUtil.simpleClassName(getClass()) + ".decode() did not read anything but decoded a message."); } if (isSingleDecode()) { break; } } } catch (DecoderException e) { throw e; } catch (Throwable cause) { throw new DecoderException(cause); } }
把字节流转化成消息后,这个消息有时候是中间消息,还需要把中间消息转化成具体的业务消息,这时候调用MessageToMessageDecoder接口。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { RecyclableArrayList out = RecyclableArrayList.newInstance(); try { if (acceptInboundMessage(msg)) { @SuppressWarnings("unchecked") I cast = (I) msg; try { decode(ctx, cast, out); } finally { ReferenceCountUtil.release(cast); } } else { out.add(msg); } } catch (DecoderException e) { throw e; } catch (Exception e) { throw new DecoderException(e); } finally { int size = out.size(); for (int i = 0; i < size; i ++) { ctx.fireChannelRead(out.get(i)); } out.recycle(); } } // HttpContentDecoder的decode方法,把HttpResponse消息转化成具体的业务消息 protected void decode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception { if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().code() == 100) { if (!(msg instanceof LastHttpContent)) { continueResponse = true; } // 100-continue response must be passed through. out.add(ReferenceCountUtil.retain(msg)); return; } if (continueResponse) { if (msg instanceof LastHttpContent) { continueResponse = false; } // 100-continue response must be passed through. out.add(ReferenceCountUtil.retain(msg)); return; } if (msg instanceof HttpMessage) { assert message == null; message = (HttpMessage) msg; decodeStarted = false; cleanup(); }
最后当ChannelPipeline到达后端的业务ChannelHandler时,拿到的消息是已经解码后的业务消息。
编码的过程也是一样,提供两个顶层接口,
1. MessageToMessageEncoder负责把业务消息转化成中间消息
2. MessageToByteEncoder负责把中间消息/业务消息转化成字节流
编码过程是一个反向的过程,这里就不重复展示代码了。MessgeToMessageEncoder/Decoder不是必须的组件,可以根据实际情况使用。
ByteToMessageDecoder / MessageToByteEncoder 是必须的组件
Netty5默认提供支持ProtoBuf, JBoss Marshalling,Java序列化等具体的编解码技术,用来把Java对象变成字节流。具体的例子请参考 http://blog.csdn.net/iter_zc/article/details/39317311