netty系列之:netty中的核心MessageToByte編碼器

flydean發表於2022-04-20

簡介

之前的文章中,我們講解了netty中從一個message轉換成為另外一個message的框架叫做MessageToMessage編碼器。但是message to message只考慮了channel中訊息在處理過程中的轉換,但是我們知道channel中最終傳輸的資料一定是ByteBuf,所以我們還需要一個message和ByteBuf相互轉換的框架,這個框架就叫做MessageToByte。

注意,這裡的byte指的是ByteBuf而不是byte這個位元組型別。

MessageToByte框架簡介

為了方便擴充套件和使用者的自定義,netty封裝了一套MessageToByte框架,這個框架中有三個核心的類,分別是MessageToByteEncoder,ByteToMessageDecoder和ByteToMessageCodec。

我們分別看一下這三個核心類的定義:

public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter 
public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler 

這三個類分別繼承自ChannelOutboundHandlerAdapter,ChannelInboundHandlerAdapter和ChannelDuplexHandler,分別表示的是向channel中寫訊息,從channel中讀訊息和一個向channel中讀寫訊息的雙向操作。

這三個類都是抽象類,接下來我們會詳細分析這三個類的具體實現邏輯。

MessageToByteEncoder

先來看encoder,如果你對比MessageToByteEncoder和MessageToMessageEncoder的原始碼實現,可以發現他們有諸多相似之處。

首先在MessageToByteEncoder中定義了一個用作訊息型別匹配的TypeParameterMatcher。

這個matcher用來匹配收到的訊息型別,如果型別匹配則進行訊息的轉換操作,否則直接將訊息寫入channel中。

和MessageToMessageEncoder不同的是,MessageToByteEncoder多了一個preferDirect欄位,這個欄位表示訊息轉換成為ByteBuf的時候是使用diret Buf還是heap Buf。

這個欄位的使用情況如下:

    protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
                               boolean preferDirect) throws Exception {
        if (preferDirect) {
            return ctx.alloc().ioBuffer();
        } else {
            return ctx.alloc().heapBuffer();
        }
    }

最後來看一下它的核心方法write:

    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
            if (acceptOutboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
                buf = allocateBuffer(ctx, cast, preferDirect);
                try {
                    encode(ctx, cast, buf);
                } finally {
                    ReferenceCountUtil.release(cast);
                }

                if (buf.isReadable()) {
                    ctx.write(buf, promise);
                } else {
                    buf.release();
                    ctx.write(Unpooled.EMPTY_BUFFER, promise);
                }
                buf = null;
            } else {
                ctx.write(msg, promise);
            }
        } catch (EncoderException e) {
            throw e;
        } catch (Throwable e) {
            throw new EncoderException(e);
        } finally {
            if (buf != null) {
                buf.release();
            }
        }
    }

上面我們已經提到了,write方法首先通過matcher來判斷是否是要接受的訊息型別,如果是的話就呼叫encode方法,將訊息物件轉換成為ByteBuf,如果不是,則直接將訊息寫入channel中。

和MessageToMessageEncoder不同的是,encode方法需要傳入一個ByteBuf物件,而不是CodecOutputList。

MessageToByteEncoder有一個需要實現的抽象方法encode如下,

    protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;

ByteToMessageDecoder

ByteToMessageDecoder用來將channel中的ByteBuf訊息轉換成為特定的訊息型別,其中Decoder中最重要的方法就是好channelRead方法,如下所示:

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            CodecOutputList out = CodecOutputList.newInstance();
            try {
                first = cumulation == null;
                cumulation = cumulator.cumulate(ctx.alloc(),
                        first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);
                callDecode(ctx, cumulation, out);
            } catch (DecoderException e) {
                throw e;
            } catch (Exception e) {
                throw new DecoderException(e);
            } finally {
                try {
                    if (cumulation != null && !cumulation.isReadable()) {
                        numReads = 0;
                        cumulation.release();
                        cumulation = null;
                    } else if (++numReads >= discardAfterReads) {
                        numReads = 0;
                        discardSomeReadBytes();
                    }

                    int size = out.size();
                    firedChannelRead |= out.insertSinceRecycled();
                    fireChannelRead(ctx, out, size);
                } finally {
                    out.recycle();
                }
            }
        } else {
            ctx.fireChannelRead(msg);
        }
    }

channelRead接收要進行訊息讀取的Object物件,因為這裡只接受ByteBuf訊息,所以在方法內部呼叫了msg instanceof ByteBuf 來判斷訊息的型別,如果不是ByteBuf型別的訊息則不進行訊息的轉換。

輸出的物件是CodecOutputList,在將ByteBuf轉換成為CodecOutputList之後,呼叫fireChannelRead方法將out物件傳遞下去。

這裡的關鍵就是如何將接收到的ByteBuf轉換成為CodecOutputList。

轉換的方法叫做callDecode,它接收一個叫做cumulation的引數,在上面的方法中,我們還看到一個和cumulation非常類似的名稱叫做cumulator。那麼他們兩個有什麼區別呢?

在ByteToMessageDecoder中cumulation是一個ByteBuf物件,而Cumulator是一個介面,這個介面定義了一個cumulate方法:

    public interface Cumulator {
        ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
    }

Cumulator用來將傳入的ByteBuf合併成為一個新的ByteBuf。

ByteToMessageDecoder中定義了兩種Cumulator,分別是MERGE_CUMULATOR和COMPOSITE_CUMULATOR。

MERGE_CUMULATOR是將傳入的ByteBuf通過memory copy的方式拷貝到目標ByteBuf cumulation中。

而COMPOSITE_CUMULATOR則是將ByteBuf新增到一個 CompositeByteBuf 的結構中,並不做memory copy,因為目標的結構比較複雜,所以速度會比直接進行memory copy要慢。

使用者要擴充套件的方法就是decode方法,用來將一個ByteBuf轉換成為其他物件:

    protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;

ByteToMessageCodec

最後要介紹的類是ByteToMessageCodec,ByteToMessageCodec表示的是message和ByteBuf之間的互相轉換,它裡面的encoder和decoder分別就是上面講到的MessageToByteEncoder和ByteToMessageDecoder。

使用者可以繼承ByteToMessageCodec來同時實現encode和decode的功能,所以需要實現encode和decode這兩個方法:

    protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;

    protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;

ByteToMessageCodec的本質就是封裝了MessageToByteEncoder和ByteToMessageDecoder,然後實現了編碼和解碼的功能。

總結

如果想實現ByteBuf和使用者自定義訊息的直接轉換,那麼選擇netty提供的上面三個編碼器是一個很好的選擇。

本文已收錄於 http://www.flydean.com/14-0-2-netty-codec-msg-to-bytebuf/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章