netty message encoder - MessageToByteEncoder

Netty channel handler ChannelHandler and adapter definition ChannelHandlerAdapter: http://donald-draper.iteye.com/blog/2386891
Netty Inbound/Outbound channel handler definition: http://donald-draper.iteye.com/blog/2387019
netty Simple Inbound Channel Handler (SimpleChannelInboundHandler): http://donald-draper.iteye.com/blog/2387772
Introduction:
In the previous article, we saw the Simple Inbound Channel Handler (SimpleChannelInboundHandler), let's take a look at it:
     Simple Inbound The channel processor SimpleChannelInboundHandler<I> has two variables inside, one is a parameter type matcher, which is used to determine whether the channel can process messages, and the other variable, autoRelease, is used to control whether to release the message when the channel is finished processing the message. The reading method channelRead first determines whether the specified message type can be processed. If so, it is delegated to channelRead0, and channelRead0 needs to be implemented by the subclass; if it returns false, the message is forwarded to the next channel processor of the Channel pipeline. ;Finally, if autoRelease is to automatically release the message, and the message has been processed, release the message.
Today we take a look at the message encoder MessageToByteEncoder:
package io.netty.handler.codec;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.TypeParameterMatcher;


/**
 * {@link ChannelOutboundHandlerAdapter} which encodes message in a stream-like fashion from one message to an
 * {@link ByteBuf}.
 *Message encoder MessageToByteEncoder, which encodes high-level message objects into low-level byte streams. The message encoder is actually an outbound channel processor.
 *
 * Example implementation which encodes {@link Integer}s to a {@link ByteBuf}.
 *The following is an example of encoding an integer into a byte buffer
 * <pre>
 *     public class IntegerEncoder extends {@link MessageToByteEncoder}<{@link Integer}> {
 *         {@code @Override}
 *         public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link ByteBuf} out)
 *                 throws {@link Exception} {
 *             out.writeInt(msg);
 *         }
 *     }
 * </pre>
 */
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {

    private final TypeParameterMatcher matcher;//Type parameter matcher
    private final boolean preferDirect;//Whether to use Direct type buf

    /**
     * see {@link #MessageToByteEncoder(boolean)} with {@code true} as boolean parameter.
     */
    protected MessageToByteEncoder() {
        this(true);//The direct type buffer is used by default. We have said about the direct type buffer in the java nio related articles
    }

    /**
     * see {@link #MessageToByteEncoder(Class, boolean)} with {@code true} as boolean value.
     Construct message encoder from message type
     */
    protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {
        this(outboundMessageType, true);
    }

    /**
     * Create a new instance which will try to detect the types to match out of the type parameter of the class.
     * Create an instance that will try to detect if the message type matches the type parameter matcher encoding
     * @param preferDirect {@code true} if a direct {@link ByteBuf} should be tried to be used as target for
     *                     the encoded messages. If {@code false} is used it will allocate a heap
     *                     {@link ByteBuf}, which is backed by an byte array.
      If the direct type buffer is used to store the encoded byte stream of the message, preferDirect is true. If preferDirect is false will assign a
      Heap type buffer, which uses a backed byte array to store the encoded byte stream of the message.
     */
    protected MessageToByteEncoder(boolean preferDirect) {
        //This sentence, after reading the previous article, should be well understood, get the message encoding, the type parameter matcher corresponding to the type parameter name I
        matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
        this.preferDirect = preferDirect;
    }

    /**
     * Create a new instance
     *
     * @param outboundMessageType   The type of messages to match
     * @param preferDirect          {@code true} if a direct {@link ByteBuf} should be tried to be used as target for
     *                              the encoded messages. If {@code false} is used it will allocate a heap
     *                              {@link ByteBuf}, which is backed by an byte array.
     */
    protected MessageToByteEncoder(Class<? extends I> outboundMessageType, boolean preferDirect) {
        //Get the type parameter matcher corresponding to the message type outboundMessageType
        matcher = TypeParameterMatcher.get(outboundMessageType);
        this.preferDirect = preferDirect;
    }

    /**
     * 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}.
     Returns true if the given message can be processed. If it returns false, pass the message to the next outbound handler on the Channel pipeline.
     */
    public boolean acceptOutboundMessage(Object msg) throws Exception {
        // Through the type parameter matcher, determine whether the message can be processed
        return matcher.match(msg);
    }
    //write message object
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
            if (acceptOutboundMessage(msg)) {//If the message can be processed by the encoder
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
		//According to the channel processor context and preferDirect, allocate a byte buf
                buf = allocateBuffer(ctx, cast, preferDirect);
                try {
		    //encode the message object to bytes buf
                    encode(ctx, cast, buf);
                } finally {
		    //Release the reference parameter corresponding to the message
                    ReferenceCountUtil.release(cast);
                }
                //If the current buffer is readable, the channel processor context writes the buffer and stores the result in the promise
                if (buf.isReadable()) {
                    ctx.write(buf, promise);
                } else {
		    //Otherwise release the buffer, write empty buf
                    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) {
	    // release buf
                buf.release();
            }
        }
    }

    /**
     * Allocate a {@link ByteBuf} which will be used as argument of {@link #encode(ChannelHandlerContext, I, ByteBuf)}.
     * Sub-classes may override this method to return {@link ByteBuf} with a perfect matching {@code initialCapacity}.
     Allocate a byte buffer for the ByteBuf parameter in the #encode method. Subclasses can override this method to return a buffer of type Direct with a suitable initialized capacity.
     */
    protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
                               boolean preferDirect) throws Exception {
        if (preferDirect) {
	    //return a buffer of direct type
            return ctx.alloc().ioBuffer();
        } else {
	   //return a heap type buffer
            return ctx.alloc().heapBuffer();
        }
    }

    /**
     * Encode a message into a {@link ByteBuf}. This method will be called for each written message that can be handled
     * by this encoder.
     * Encode message into bytes buf. This method is called when the write message can be processed by the current encoder.
     * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to
     The channel handler context to which the message encoder belongs
     * @param msg the message to encode the message to encode
     * @param out           the {@link ByteBuf} into which the encoded message will be written 字节buffer
     * @throws Exception    is thrown if an error occurs
     */
    protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
    //Get whether to use the direct type buffer to store the encoded byte sequence of the message
    protected boolean isPreferDirect() {
        return preferDirect;
    }
}


Let's analyze the write message object
//write the message object
  
@Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
            if (acceptOutboundMessage(msg)) {//If the message can be processed by the encoder
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
		//According to the channel processor context and preferDirect, allocate a byte buf
                buf = allocateBuffer(ctx, cast, preferDirect);
                try {
		    //encode the message object to bytes buf
                    encode(ctx, cast, buf);
                } finally {
		    //Release the reference parameter corresponding to the message
                    ReferenceCountUtil.release(cast);
                }
                //If the current buffer is readable, the channel processor context writes the buffer
                if (buf.isReadable()) {
                    ctx.write(buf, promise);
                } else {
		    //Otherwise release the buffer, write empty buf
                    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) {
	    // release buf
                buf.release();
            }
        }
    }

There is a code snippet we need to pay attention to:
//If the current buffer is readable, the channel processor context writes the buffer and stores the result in the promise
if (buf.isReadable()) {
    ctx.write(buf, promise);
} else {
    //Otherwise release the buffer, write empty buf
    buf.release();
    ctx.write(Unpooled.EMPTY_BUFFER, promise);
}

//ByteBuf
/**
 * Returns {@code true}
 * if and only if {@code (this.writerIndex - this.readerIndex)} is greater
 * than {@code 0}.
 If the write index of byte buf is greater than the read index, it can be read
 */
public abstract boolean isReadable();

We have time to talk about byte buf later.
//Unpooled
private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT;
 /**
 * A buffer whose capacity is {@code 0}.
 Byte buf with empty capacity, this will be followed by byte buf
 */
public static final ByteBuf EMPTY_BUFFER = ALLOC.buffer(0, 0);

//ChannelHandlerContext
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {

//ChannelOutboundInvoker
/**
     * Request to write a message via this {@link ChannelHandlerContext} through the {@link ChannelPipeline}.
     * This method will not request to actual flush, so be sure to call {@link #flush()}
     * once you want to request to flush all pending data to the actual transport.
     Through the Channel handler context, request to write a message to the Channel pipeline. If the method doesn't actually refresh, if you want to request
     To flush all data waiting to be sent to the actual transport, the flush method must be called once.
     */
    ChannelFuture write(Object msg, ChannelPromise promise);

Let's take a brief look at the one-byte buf allocation function:
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
                               boolean preferDirect) throws Exception {
        if (preferDirect) {
	    //return a buffer of direct type
            return ctx.alloc().ioBuffer();
        } else {
	   //return a heap type buffer
            return ctx.alloc().heapBuffer();
        }
    }

//ChannelHandlerContext
/**
     * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s.
     */
    ByteBufAllocator alloc();


//ByteBufAllocator

public interface ByteBufAllocator {
    ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
     /**
     * Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O.
     */
    ByteBuf ioBuffer();
    /**
     * Allocate a heap {@link ByteBuf}.
     */
    ByteBuf heapBuffer();
...
}

Allocate byte buf, actually delegate to the ByteBufAllocator of the channel handler context.
Summary:
The message encoder MessageToByteEncoder is actually an outbound channel processor. There is a type parameter processor TypeParameterMatcher inside, which is used to determine whether the message can be processed by the current encoder. If it cannot be passed to the next channel processor on the Channel pipeline ; a preferDirect parameter that determines, when encoding a message as a sequence of bytes, whether it should be stored in a direct or heap type of byte buffer. The main method of the message encoder is the write method. The write method first determines whether the message can be processed by the current encoder. If the message can be processed by the encoder, according to the channel processor context and preferDirect, allocate a byte buf, delegate the encode method, and encode The message object is converted to byte buf, and the encode method needs to be implemented by the subclass; the reference parameter corresponding to the release message, if the current buffer is readable, the channel processor context writes the buffer, otherwise releases the buffer, writes empty buf, and finally releases the buf. The message encoder MessageToByteEncoder is actually an outbound channel processor, which is different from the message encoder in Mina. The message encoder in Mina should be assembled with the decoder into a codec factory filter and added to the filter chain, and The codec factory filter is sequential in the filter chain. In channel Mina, the encoder and the channel handler are two concepts. The encoder in Netty is actually an outbound channel processor, mainly through the type parameter matcher TypeParameterMatcher, to determine whether the message can be processed by the encoder.


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326312156&siteId=291194637