Netty5源码分析(六) -- CodeC编解码分析

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;
        }
    }

Netty框架拿到ByteBuf之后就拿到了读到的字节数据,就可以调用ByteToMessageDecoder来把字节流转化成业务消息。把字节流转化成业务消息也就是如何分帧的问题。具体有几类:

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);
        }
    }

Netty5源码分析(六) -- CodeC编解码分析_第1张图片


把字节流转化成消息后,这个消息有时候是中间消息,还需要把中间消息转化成具体的业务消息,这时候调用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();
        }

Netty5源码分析(六) -- CodeC编解码分析_第2张图片


最后当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















你可能感兴趣的:(netty)