This is the 18th day of my participation in the August Genwen Challenge.More challenges in August

preface

Last article we explained the decoder related knowledge, which also mentioned the definition of encoder.

Netty source code analysis series (12) Netty decoder

The encoder is used to convert outbound data from one format to another, so it implements ChannelOutboundHandler, similar to a decoder. Netty also provides a set of classes to help developers quickly get started with the encoder. Of course, these classes provide the opposite of the decoder method, as shown below:

  • Encoding from message to byte (MessageToByteEncoder).
  • Encode from message to message (MessageToMessageEncoder).

MessageToByteEncoder abstract class

In the last article, we saw how to use ByteToMessageDecoder to convert bytes into messages. Now we can use MessageToByteEncoder to do the opposite.

MessageToByteEncoder core code is as follows:

public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {
    private final TypeParameterMatcher matcher;
    private final boolean preferDirect;

    protected MessageToByteEncoder(a) {
        this(true);
    }

    protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {
        this(outboundMessageType, true);
    }

    protected MessageToByteEncoder(boolean preferDirect) {
        this.matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
        this.preferDirect = preferDirect;
    }

    protected MessageToByteEncoder(Class<? extends I> outboundMessageType, boolean preferDirect) {
        this.matcher = TypeParameterMatcher.get(outboundMessageType);
        this.preferDirect = preferDirect;
    }

    public boolean acceptOutboundMessage(Object msg) throws Exception {
        return this.matcher.match(msg);
    }

    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;

        try {
            if (this.acceptOutboundMessage(msg)) {
                I cast = msg;
                buf = this.allocateBuffer(ctx, msg, this.preferDirect);

                try {
                    this.encode(ctx, cast, buf);
                } finally {
                    ReferenceCountUtil.release(msg);
                }

                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 var17) {
            throw var17;
        } catch (Throwable var18) {
            throw new EncoderException(var18);
        } finally {
            if(buf ! =null) { buf.release(); }}}protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, I msg, boolean preferDirect) throws Exception {
        return preferDirect ? ctx.alloc().ioBuffer() : ctx.alloc().heapBuffer();
    }

    protected abstract void encode(ChannelHandlerContext var1, I var2, ByteBuf var3) throws Exception;

    protected boolean isPreferDirect(a) {
        return this.preferDirect; }}Copy the code

In the MessageToByteEncoder abstract class, the only thing you care about is the encode method, which is the only abstract method that developers need to implement. It is called with the outbound message, encoding the message as ByteBuf, and then forwarding the ByteBuf to the next ChannelOutboundHandler in the ChannelPipeline.

The following is an example of MessageToByteEncoder:

public class ShortToByteEncoder extends MessageToByteEncoder<Short> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {
        out.writeShort(msg);// Write Short to a binary byte stream in ByteBuf}}Copy the code

In the example above, the ShortToByteEncoder receives Short messages, encodes them, and writes them to ByteBuf. ByteBuf is then forwarded to the next ChannelOutboundHandler in the ChannelPipeline, with each Short occupying two bytes of ByteBuf.

The realization of ShortToByteEncoder consists of the following two steps:

  • Implementation inherits fromMessageToByteEncoder.
  • Write Short to ByteBuf.

The above example processing flow chart is as follows:

Netty also provides many subclasses of the MessageToByteEncoder class to help developers implement their own encoders, such as:

MessageToMessageEncoder abstract class

The MessageToMessageEncoder abstract class is used to convert outbound data from one message to another.

The core source code is as follows:

public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {

    private final TypeParameterMatcher matcher;

    /** * Create a new instance which will try to detect the types to match out of the type parameter of the class. */
    protected MessageToMessageEncoder(a) {
        matcher = TypeParameterMatcher.find(this, MessageToMessageEncoder.class, "I");
    }

    /**
     * Create a new instance
     *
     * @param outboundMessageType   The type of messages to match and so encode
     */
    protected MessageToMessageEncoder(Class<? extends I> outboundMessageType) {
        matcher = TypeParameterMatcher.get(outboundMessageType);
    }

    /**
     * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
     * {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     */
    public boolean acceptOutboundMessage(Object msg) throws Exception {
        return matcher.match(msg);
    }

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

                if (out.isEmpty()) {
                    throw new EncoderException(
                            StringUtil.simpleClassName(this) + " must produce at least one message."); }}else{ ctx.write(msg, promise); }}catch (EncoderException e) {
            throw e;
        } catch (Throwable t) {
            throw new EncoderException(t);
        } finally {
            if(out ! =null) {
                try {
                    final int sizeMinusOne = out.size() - 1;
                    if (sizeMinusOne == 0) {
                        ctx.write(out.getUnsafe(0), promise);
                    } else if (sizeMinusOne > 0) {
                        // Check if we can use a voidPromise for our extra writes to reduce GC-Pressure
                        // See https://github.com/netty/netty/issues/2525
                        if (promise == ctx.voidPromise()) {
                            writeVoidPromise(ctx, out);
                        } else{ writePromiseCombiner(ctx, out, promise); }}}finally{ out.recycle(); }}}}private static void writeVoidPromise(ChannelHandlerContext ctx, CodecOutputList out) {
        final ChannelPromise voidPromise = ctx.voidPromise();
        for (int i = 0; i < out.size(); i++) { ctx.write(out.getUnsafe(i), voidPromise); }}private static void writePromiseCombiner(ChannelHandlerContext ctx, CodecOutputList out, ChannelPromise promise) {
        final PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
        for (int i = 0; i < out.size(); i++) {
            combiner.add(ctx.write(out.getUnsafe(i)));
        }
        combiner.finish(promise);
    }


    protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}
Copy the code

Like the MessageToByteEncoder abstract class, MessageToMessageEncoder’s only concern is the encode method, which is the only abstract method that developers need to implement. For each message written using write(), the message is called to encode the message as one or more new outbound messages, and then the encoded message is forwarded.

Here is an example using MessageToMessageEncoder:

public class IntegerToStringEncoder extends MessageToMessageEncoder <Integer> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception { out.add(String.ValueOf(msg)); }}Copy the code

The above example encodes an Integer message as a String message in two main steps:

  • Implementation inherits fromMessageToMessageEncoder.
  • Convert Integer to String and add it to MessageBuf.

The processing flow of the above example is shown below:

conclusion

Through the above article explained, we should have a certain understanding of encoder and decoder, in fact, for encoding and decoding, Netty also provides a third way, that is codec. We’ll talk about that in the next video.

At the end

I am a coder who is being beaten and still trying to move on. If this article is helpful to you, remember to like and follow yo, thanks!