簡介
之前的文章中,我們講解了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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!