Netty源碼分析-ByteToMessageDecoder

ByteToMessageDecoder

ByteToMessageDecoder是一種ChannelInboundHandler,可以稱為解碼器,負責將byte字節流住(ByteBuf)轉換成一種Message,Message是應用可以自己定義的一種Java對象。

例如應用中使用protobuf協議,則可以將byte轉換為Protobuf對象。然后交給后面的Handler來處理。

使用示例, 下面這段代碼先將收到的數據按照換行符分割成一段一段的,然后將byte轉換成String, 再將String轉換成int, 然后把int加一后寫回。

image.png

ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
    bootstrap.channel(NioServerSocketChannel.class)
            .handler(new LoggingHandler(LogLevel.DEBUG))
            .group(bossGroup, workerGroup)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LineBasedFrameDecoder(1024))
                            .addLast(new ByteToStringDecoder())
                            .addLast(new StringToIntegerDecoder())
                            .addLast(new IntegerToByteEncoder())
                            .addLast(new IntegerIncHandler());
                }
            });
    ChannelFuture bind = bootstrap.bind(8092);
    bind.sync();
    bind.channel().closeFuture().sync();
} finally {
    bossGroup.shutdownGracefully().sync();
    workerGroup.shutdownGracefully().sync();
}

這里的ChannelPipeline的組織結構是
1. ByteToStringDecoder - 將byte轉換成String的Decoder
2. StringToIntegerDecoder - String轉換成Integer對象的Decoder
3. IntegerToByteEncoder - Integer轉換成byte的Encoder
4. IntegerIncHandler - 將接受到的int加一后返回

ByteToStringDecoder

ByteToStringMessageDecoder繼承于ByteToMessageDecoder,并實現了ByteToMessageDecoder的
decode(ChannelHandlerContext ctx, ByteBuf in, java.util.List<java.lang.object style="box-sizing: border-box;"> out)方法。</java.lang.object>
decode方法實現中要求將ByteBuf中的數據進行解碼然后將解碼后的對象增加到list中

public class ByteToStringDecoder extends ByteToMessageDecoder {
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        byte[] data = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(data);
        list.add(new String(data, StandardCharsets.UTF_8));
    }
}

ByteToStringMessageDecoder繼承于ByteToMessageDecoder,并實現了ByteToMessageDecoder的
decode(ChannelHandlerContext ctx, ByteBuf in, java.util.List<java.lang.Object> out)方法。
decode方法實現中要求將ByteBuf中的數據進行解碼然后將解碼后的對象增加到list中

ByteToMessageDecoder

ByteToMessageDecoder繼承了ChannelInboundHandlerAdapter所以是一個處理Inbound事件的Handler。
其內部保存一個Cumulator用于保存待解碼的ByteBuf,然后不斷調用子類需要實現的抽象方法decode去取出byte數據轉換處理。

/**
     * Cumulate {@link ByteBuf}s.
     */
    public interface Cumulator {
        /**
         * Cumulate the given {@link ByteBuf}s and return the {@link ByteBuf} that holds the cumulated bytes.
         * The implementation is responsible to correctly handle the life-cycle of the given {@link ByteBuf}s and so
         * call {@link ByteBuf#release()} if a {@link ByteBuf} is fully consumed.
         */
        ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
    }

Cumulator有兩種實現,MERGE_CUMULATOR和COMPOSITE_CMUMULATOR。MERGE_CUMULATOR通過memory copy的方法將in中的數據復制寫入到cumulation中。COMPOSITE_CUMULATOR采取的是類似鏈表的方式,沒有進行memory copy, 通過一種CompositeByteBuf來實現,在某些場景下會更適合。默認采用的是MERGE_CUMULATOR。

public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
        @Override
        public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
            final ByteBuf buffer;
            if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
                    || cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
                // Expand cumulation (by replace it) when either there is not more room in the buffer
                // or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
                // duplicate().retain() or if its read-only.
                // 如果cumulation是只讀的、或者要超過capacity了,或者還有其他地方在引用, 則都通過創建一個新的byteBuf的方式來擴容ByteBuf
                buffer = expandCumulation(alloc, cumulation, in.readableBytes());
            } else {
                buffer = cumulation;
            }
            buffer.writeBytes(in);
            in.release();
            return buffer;
        }
    };

ByteToMessageDecoder中最主要的部分在channelRead處理上

  1. 收到一個msg后先判斷是否是ByteBuf類型,是的情況創建一個CodecOutputList(也是一種list)保存轉碼后的對象列表
  2. 如果cumulation為null則把msg設置為cumulation,否則合并到cumulation里
  3. 調用callDecode方法,嘗試解碼
  4. finally中如果cumulation已經讀完了,就release并置為null等待gc
  5. 調用fireChannelRead將解碼后的out傳遞給后面的Handler
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            CodecOutputList out = CodecOutputList.newInstance();
            try {
                ByteBuf data = (ByteBuf) msg;
                first = cumulation == null;
                if (first) {
                    cumulation = data;
                } else {
                    cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
                }
                callDecode(ctx, cumulation, out);
            } catch (DecoderException e) {
                throw e;
            } catch (Exception e) {
                throw new DecoderException(e);
            } finally {
                if (cumulation != null && !cumulation.isReadable()) {
                    numReads = 0;
                    cumulation.release();
                    cumulation = null;
                } else if (++ numReads >= discardAfterReads) {
                    // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                    // See https://github.com/netty/netty/issues/4275
                    numReads = 0;
                    discardSomeReadBytes();
                }

                int size = out.size();
                decodeWasNull = !out.insertSinceRecycled();
                fireChannelRead(ctx, out, size);
                out.recycle();
            }
        } else {
            ctx.fireChannelRead(msg);
        }
    }

callDecode中不斷執行抽象decode(ctx, in, out)方法直到in可讀數據沒有減少或當前handler被remove。

 protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        try {
            while (in.isReadable()) {
                int outSize = out.size();
                if (outSize > 0) {
                    fireChannelRead(ctx, out, outSize);
                    out.clear();
                   // 檢查當前handler是否被remove了
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }

                int oldInputLength = in.readableBytes();
                decodeRemovalReentryProtection(ctx, in, out);
                // 檢查當前handler是否被remove了
                if (ctx.isRemoved()) {
                    break;
                }

                if (outSize == out.size()) {
                    if (oldInputLength == in.readableBytes()) {
                        break;
                    } else {
                        continue;
                    }
                }

                if (oldInputLength == in.readableBytes()) { // 這種情況是解碼出了對象但是并沒有移動in的readIndex
                    throw new DecoderException(
                            StringUtil.simpleClassName(getClass()) +
                                    ".decode() did not read anything but decoded a message.");
                }

                if (isSingleDecode()) {
                    break;
                }
            }
        } ...
    }

fireChannelRead(ctx, msgs, numElements)的處理方式是對每個解碼后的消息進行fireChannelRead,交給下一個Handler處理

/**
    static void fireChannelRead(ChannelHandlerContext ctx, List<Object> msgs, int numElements) {
        if (msgs instanceof CodecOutputList) {
            fireChannelRead(ctx, (CodecOutputList) msgs, numElements);
        } else {
            for (int i = 0; i < numElements; i++) {
                ctx.fireChannelRead(msgs.get(i));
            }
        }
    }
    static void fireChannelRead(ChannelHandlerContext ctx, CodecOutputList msgs, int numElements) {
        for (int i = 0; i < numElements; i ++) {
            ctx.fireChannelRead(msgs.getUnsafe(i));
        }
    }

以上就是ByteToMessageDecoder的主要處理部分。關于Netty,面試中會喜歡問道“粘包/拆包”問題,指的是一個消息在網絡中是二進制byte流的形式傳過去的,接收方如何判斷一個消息是否讀完、哪里是分割點等,這些可以通過Netty中提供的一些Decoder來實現,例如DelimiterBasedFrameDecoder,FixedLengthFrameDecoder, LengthFieldBasedFrameDecoder。其中最常見的應該是LengthFieldBasedFrameDecoder了,因為自定義的協議中通常會有一個協議頭,里面有一個字段描述一個消息的大小長度,然后接收方就能知道消息讀到什么時候是讀完一個Frame了。這些解碼器會在后續的文章中介紹。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容