Introduction
In the previous article, we explained that the framework in netty to convert from one message to another is called the MessageToMessage encoder. But message to message only considers the conversion of messages in the channel during processing, but we know that the final data transmitted in the channel must be ByteBuf, so we also need a framework for mutual conversion between message and ByteBuf, which is called MessageToByte.
Note that byte here refers to ByteBuf rather than the byte type.
Introduction to the MessageToByte Framework
In order to facilitate expansion and user customization, netty encapsulates a set of MessageToByte framework. There are three core classes in this framework, namely MessageToByteEncoder, ByteToMessageDecoder and ByteToMessageCodec.
Let's take a look at the definitions of these three core classes:
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter
public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler
These three classes inherit from ChannelOutboundHandlerAdapter, ChannelInboundHandlerAdapter and ChannelDuplexHandler respectively, which respectively represent the two-way operation of writing messages to the channel, reading messages from the channel, and reading and writing messages to the channel.
These three classes are abstract classes. Next, we will analyze the specific implementation logic of these three classes in detail.
MessageToByteEncoder
Let's first look at the encoder. If you compare the source code implementation of MessageToByteEncoder and MessageToMessageEncoder, you can find that they have many similarities.
First, a TypeParameterMatcher for message type matching is defined in MessageToByteEncoder.
This matcher is used to match the received message type. If the type matches, the message conversion operation is performed, otherwise the message is directly written to the channel.
Different from MessageToMessageEncoder, MessageToByteEncoder has an additional preferDirect field, which indicates whether to use diret Buf or heap Buf when the message is converted into ByteBuf.
The usage of this field is as follows:
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
boolean preferDirect) throws Exception {
if (preferDirect) {
return ctx.alloc().ioBuffer();
} else {
return ctx.alloc().heapBuffer();
}
}
Finally, let's take a look at its core method 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();
}
}
}
As we have mentioned above, the write method first uses the matcher to determine whether it is the type of message to be accepted. If so, it calls the encode method to convert the message object into a ByteBuf. If not, it directly writes the message to the channel.
Unlike MessageToMessageEncoder, the encode method needs to pass in a ByteBuf object instead of CodecOutputList.
MessageToByteEncoder has an abstract method encode that needs to be implemented as follows,
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
ByteToMessageDecoder
ByteToMessageDecoder is used to convert the ByteBuf message in the channel into a specific message type. The most important method in the Decoder is the channelRead method, as shown below:
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 receives the Object object to be read. Because only ByteBuf messages are accepted here, msg instanceof ByteBuf
is called inside the method to judge the type of the message. If it is not a ByteBuf type of message, the message conversion will not be performed.
The output object is CodecOutputList. After converting ByteBuf into CodecOutputList, call the fireChannelRead method to pass the out object.
The key here is how to convert the received ByteBuf into a CodecOutputList.
The conversion method is called callDecode, which receives a parameter called accumulation. In the above method, we also see a very similar name to accumulation called accumulation. So what's the difference between the two?
In ByteToMessageDecoder, accumulation is a ByteBuf object, and Cumulator is an interface that defines a accumulate method:
public interface Cumulator {
ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
}
Cumulator is used to merge incoming ByteBufs into a new ByteBuf.
Two kinds of Cumulators are defined in ByteToMessageDecoder, namely MERGE_CUMULATOR and COMPOSITE_CUMULATOR.
MERGE_CUMULATOR is to copy the incoming ByteBuf to the target ByteBuf accumulation by means of memory copy.
And COMPOSITE_CUMULATOR is to add ByteBuf to a CompositeByteBuf structure, and does not do memory copy, because the structure of the target is more complex, so the speed will be slower than direct memory copy.
The method that the user wants to extend is the decode method, which is used to convert a ByteBuf into other objects:
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
ByteToMessageCodec
The last class to be introduced is ByteToMessageCodec. ByteToMessageCodec represents the mutual conversion between message and ByteBuf. The encoder and decoder in it are the MessageToByteEncoder and ByteToMessageDecoder mentioned above.
Users can inherit ByteToMessageCodec to realize the functions of encode and decode at the same time, so it is necessary to implement the two methods of encode and 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;
The essence of ByteToMessageCodec is to encapsulate MessageToByteEncoder and ByteToMessageDecoder, and then implement the functions of encoding and decoding.
Summarize
If you want to achieve direct conversion between ByteBuf and user-defined messages, then choosing the above three encoders provided by netty is a good choice.
This article has been included in http://www.flydean.com/14-0-2-netty-codec-msg-to-bytebuf/
The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!
Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。