package io.netty.handler.codec.ReplayingDecoder

package xmg.quest.netty.core;
/**
* @author 作者 : xuminggang
* @version 創建時間:2020年6月8日 上午10:05:15
* 
*/
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.DecoderException;
import io.netty.util.Signal;
import io.netty.util.internal.StringUtil;

import java.util.List;

/**
 * {@link ByteToMessageDecoder}的一個特殊變體,在阻塞I/O範示中可以實現一種非阻塞的解碼器。
 * <p>
 * {@link ReplayingDecoder} 和 {@link ReplayingDecoder} 的最大區別在{@link ReplayingDecoder}允許
 * 	你實現 {@code decode()} 和 {@code decodeLast()} 方法將像所有需要的字節都已經被接收到了一樣,而不必去檢查所需
 * 	字節是不是可用或存在。
 * 	舉例,下面是 {@link ByteToMessageDecoder} 的實現。
 * <pre>
 * public class IntegerHeaderFrameDecoder extends {@link ByteToMessageDecoder} {
 *
 *   {@code @Override}
 *   protected void decode({@link ChannelHandlerContext} ctx,
 *                           {@link ByteBuf} buf, List&lt;Object&gt; out) throws Exception {
 *
 *     if (buf.readableBytes() &lt; 4) {
 *        return;
 *     }
 *
 *     buf.markReaderIndex();
 *     int length = buf.readInt();
 *
 *     if (buf.readableBytes() &lt; length) {
 *        buf.resetReaderIndex();
 *        return;
 *     }
 *
 *     out.add(buf.readBytes(length));
 *   }
 * }
 * </pre>
 * 	下面使用 {@link ReplayingDecoder} 簡化了實現:
 * <pre>
 * public class IntegerHeaderFrameDecoder
 *      extends {@link ReplayingDecoder}&lt;{@link Void}&gt; {
 *
 *   protected void decode({@link ChannelHandlerContext} ctx,
 *                           {@link ByteBuf} buf) throws Exception {
 *
 *     out.add(buf.readBytes(buf.readInt()));
 *   }
 * }
 * </pre>
 *
 * <h3>它是怎麼工作的?</h3>
 * <p>
 * {@link ReplayingDecoder} 傳遞了一個特殊的 {@link ByteBuf} 的實現,當緩衝中沒有足夠的數據
 * 	時候就來拋出一個 確定類型的 {@link Error}。 在上面的 {@code IntegerHeaderFrameDecoder}例子中
 * 	當調用 {@code buf.readInt()}你只是假設buffer中有4個或4個以上字節,如果buffer中真的有4個
 * 	字節,它將返回你期待的integer頭。否則,就會拋出一個{@link Error}並且 控制會返回給 {@link ReplayingDecoder}。
 * 	如果 {@link ReplayingDecoder} 捕獲到這個 {@link Error},就會將 buffer的 {@code readerIndex} 重置爲
 * 	‘初始的’位置(如,buffer的起始位置)並且當buffer收到數據時,再次調用 {@code decode(..)}方法。
 * <p>
 *	請注意 {@link ReplayingDecoder}總是拋出相同的緩衝 {@link Error}實例,從而來避免創建一個新 {@link Error}的負擔
 *	並每次拋出時都添加堆棧信息。
 *
 * <h3>限制</h3>
 * <p>
 * 	以簡潔爲代價,{@link ReplayingDecoder} 施加了幾個限制:
 * <ul>
 * <li>某些buffer操作是被禁止的。</li>
 * <li>如果網絡很慢而且消息格式很複雜,性能可能會很差。在這種情況下,你的解碼器不得不周而復始的解碼消息相同的部分。</li>
 * <li>你必須記住 {@code decode(..)} 方法會調用很多次來解碼一個單獨的消息。例如,下面的代碼就可能不能正常使用:
 * <pre> public class MyDecoder extends {@link ReplayingDecoder}&lt;{@link Void}&gt; {
 *
 *   private final Queue&lt;Integer&gt; values = new LinkedList&lt;Integer&gt;();
 *
 *   {@code @Override}
 *   public void decode(.., {@link ByteBuf} buf, List&lt;Object&gt; out) throws Exception {
 *
 *     // 一個消息包含了兩個integers.
 *     values.offer(buf.readInt());
 *     values.offer(buf.readInt());
 *
 *     // 這個斷言可能會間歇性的失敗, 因爲values.offer() 可能會被調用兩次以上
 *     assert values.size() == 2;
 *     out.add(values.poll() + values.poll());
 *   }
 * }</pre>
 *      	正確的實現是下面這個樣子,你還可以使用檢查點的方式來去實現,在下一段中會詳細解釋。
 * <pre> public class MyDecoder extends {@link ReplayingDecoder}&lt;{@link Void}&gt; {
 *
 *   private final Queue&lt;Integer&gt; values = new LinkedList&lt;Integer&gt;();
 *
 *   {@code @Override}
 *   public void decode(.., {@link ByteBuf} buf, List&lt;Object&gt; out) throws Exception {
 *
 *     // 重置變量的狀態,因爲最後一次階段解碼可能會改變變量狀態
 *     values.clear();
 *
 *     // 一個消息包含了兩個integers.
 *     values.offer(buf.readInt());
 *     values.offer(buf.readInt());
 *
 *     // 現在我們知道這個斷言永不會失敗。
 *     assert values.size() == 2;
 *     out.add(values.poll() + values.poll());
 *   }
 * }</pre>
 *     </li>
 * </ul>
 *
 * <h3>性能改進</h3>
 * <p>
 * 	幸好,一個複雜的解碼器實現的性能可以藉助 {@code checkpoint()}的方法來改進。 {@code checkpoint()} 
 * 	這個方法會更新buffer的初始位置,這樣 當,{@link ReplayingDecoder} 就可以重新定位buffer的
 * 	{@code readerIndex} 到你上一次調用{@code checkpoint()}方法時的位置。
 *
 * <h4>使用一個{@link Enum} 調用 {@code checkpoint(T)} </h4>
 * <p>
 * 	儘管你自己可以僅僅使用 {@code checkpoint()} 方法來管理解碼器的狀態,但是最簡單的方式來管理解碼器的狀態是
 * 	創建一個 {@link Enum} 類型來代表當前解碼器的狀態,並可以在狀態改變時調用 {@code checkpoint(T)} 方法。
 * 	你可以根據自己需要設置任意多的狀態,這個取決於你想要解碼的消息的複雜性:
 *
 * <pre>
 * public enum MyDecoderState {
 *   READ_LENGTH,
 *   READ_CONTENT;
 * }
 *
 * public class IntegerHeaderFrameDecoder
 *      extends {@link ReplayingDecoder}&lt;<strong>MyDecoderState</strong>&gt; {
 *
 *   private int length;
 *
 *   public IntegerHeaderFrameDecoder() {
 *     // 設置初始狀態.
 *     <strong>super(MyDecoderState.READ_LENGTH);</strong>
 *   }
 *
 *   {@code @Override}
 *   protected void decode({@link ChannelHandlerContext} ctx,
 *                           {@link ByteBuf} buf, List&lt;Object&gt; out) throws Exception {
 *     switch (state()) {
 *     case READ_LENGTH:
 *       length = buf.readInt();
 *       <strong>checkpoint(MyDecoderState.READ_CONTENT);</strong>
 *     case READ_CONTENT:
 *       ByteBuf frame = buf.readBytes(length);
 *       <strong>checkpoint(MyDecoderState.READ_LENGTH);</strong>
 *       out.add(frame);
 *       break;
 *     default:
 *       throw new Error("Shouldn't reach here.");
 *     }
 *   }
 * }
 * </pre>
 *
 * <h4>調用不帶參數的 {@code checkpoint()} </h4>
 * <p>
 * 	另外一種管理解碼器狀態的方式是你自己管理。
 * <pre>
 * public class IntegerHeaderFrameDecoder
 *      extends {@link ReplayingDecoder}&lt;<strong>{@link Void}</strong>&gt; {
 *
 *   <strong>private boolean readLength;</strong>
 *   private int length;
 *
 *   {@code @Override}
 *   protected void decode({@link ChannelHandlerContext} ctx,
 *                           {@link ByteBuf} buf, List&lt;Object&gt; out) throws Exception {
 *     if (!readLength) {
 *       length = buf.readInt();
 *       <strong>readLength = true;</strong>
 *       <strong>checkpoint();</strong>
 *     }
 *
 *     if (readLength) {
 *       ByteBuf frame = buf.readBytes(length);
 *       <strong>readLength = false;</strong>
 *       <strong>checkpoint();</strong>
 *       out.add(frame);
 *     }
 *   }
 * }
 * </pre>
 *
 * <h3>用管道里的另一個解碼器替換一個解碼器</h3>
 * <p>
 * 	如果你打算編寫一個協議的多路複用器,你可能想要用另外一個替換一個 {@link ReplayingDecoder},
 * 	{@link ByteToMessageDecoder} 或者 {@link MessageToMessageDecoder}(實際的協議解碼器)來替換一個
 * 	已存在的 {@link ReplayingDecoder} (協議探測器)。
 *	不可能直接調用 {@link ChannelPipeline#replace(ChannelHandler, String, ChannelHandler)},但是
 *	需要一些額外的步驟:
 * It is not possible to achieve this simply by calling
 * {@link ChannelPipeline#replace(ChannelHandler, String, ChannelHandler)}, but
 * some additional steps are required:
 * <pre>
 * public class FirstDecoder extends {@link ReplayingDecoder}&lt;{@link Void}&gt; {
 *
 *     {@code @Override}
 *     protected void decode({@link ChannelHandlerContext} ctx,
 *                             {@link ByteBuf} buf, List&lt;Object&gt; out) {
 *         ...
 *         // Decode the first message
 *         Object firstMessage = ...;
 *
 *         // Add the second decoder
 *         ctx.pipeline().addLast("second", new SecondDecoder());
 *
 *         if (buf.isReadable()) {
 *             // Hand off the remaining data to the second decoder
 *             out.add(firstMessage);
 *             out.add(buf.readBytes(<b>super.actualReadableBytes()</b>));
 *         } else {
 *             // Nothing to hand off
 *             out.add(firstMessage);
 *         }
 *         // Remove the first decoder (me)
 *         ctx.pipeline().remove(this);
 *     }
 * </pre>
 * @param <S>
 *			狀態類型:通常爲 {@link Enum}; 如果不需要狀態管理時使用 {@link Void}
 */
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder {

    static final Signal REPLAY = Signal.valueOf(ReplayingDecoder.class, "REPLAY");

    private final ReplayingDecoderByteBuf replayable = new ReplayingDecoderByteBuf();
    private S state;
    private int checkpoint = -1;

    /**
     * Creates a new instance with no initial state (i.e: {@code null}).
     */
    protected ReplayingDecoder() {
        this(null);
    }

    /**
     * Creates a new instance with the specified initial state.
     */
    protected ReplayingDecoder(S initialState) {
        state = initialState;
    }

    /**
     * Stores the internal cumulative buffer's reader position.
     */
    protected void checkpoint() {
        checkpoint = internalBuffer().readerIndex();
    }

    /**
     * Stores the internal cumulative buffer's reader position and updates
     * the current decoder state.
     */
    protected void checkpoint(S state) {
        checkpoint();
        state(state);
    }

    /**
     * Returns the current state of this decoder.
     * @return the current state of this decoder
     */
    protected S state() {
        return state;
    }

    /**
     * Sets the current state of this decoder.
     * @return the old state of this decoder
     */
    protected S state(S newState) {
        S oldState = state;
        state = newState;
        return oldState;
    }

    @Override
    final void channelInputClosed(ChannelHandlerContext ctx, List<Object> out) throws Exception {
        try {
            replayable.terminate();
            if (cumulation != null) {
                callDecode(ctx, internalBuffer(), out);
            } else {
                replayable.setCumulation(Unpooled.EMPTY_BUFFER);
            }
            decodeLast(ctx, replayable, out);
        } catch (Signal replay) {
            // Ignore
            replay.expect(REPLAY);
        }
    }

    @Override
    protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        replayable.setCumulation(in);
        try {
            while (in.isReadable()) {
                int oldReaderIndex = checkpoint = in.readerIndex();
                int outSize = out.size();

                if (outSize > 0) {
                    fireChannelRead(ctx, out, outSize);
                    out.clear();

                    // Check if this handler was removed before continuing with decoding.
                    // If it was removed, it is not safe to continue to operate on the buffer.
                    //
                    // See:
                    // - https://github.com/netty/netty/issues/4635
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }

                S oldState = state;
                int oldInputLength = in.readableBytes();
                try {
                    decodeRemovalReentryProtection(ctx, replayable, out);

                    // Check if this handler was removed before continuing the loop.
                    // If it was removed, it is not safe to continue to operate on the buffer.
                    //
                    // See https://github.com/netty/netty/issues/1664
                    if (ctx.isRemoved()) {
                        break;
                    }

                    if (outSize == out.size()) {
                        if (oldInputLength == in.readableBytes() && oldState == state) {
                            throw new DecoderException(
                                    StringUtil.simpleClassName(getClass()) + ".decode() must consume the inbound " +
                                    "data or change its state if it did not decode anything.");
                        } else {
                            // Previous data has been discarded or caused state transition.
                            // Probably it is reading on.
                            continue;
                        }
                    }
                } catch (Signal replay) {
                    replay.expect(REPLAY);

                    // Check if this handler was removed before continuing the loop.
                    // If it was removed, it is not safe to continue to operate on the buffer.
                    //
                    // See https://github.com/netty/netty/issues/1664
                    if (ctx.isRemoved()) {
                        break;
                    }

                    // Return to the checkpoint (or oldPosition) and retry.
                    int checkpoint = this.checkpoint;
                    if (checkpoint >= 0) {
                        in.readerIndex(checkpoint);
                    } else {
                        // Called by cleanup() - no need to maintain the readerIndex
                        // anymore because the buffer has been released already.
                    }
                    break;
                }

                if (oldReaderIndex == in.readerIndex() && oldState == state) {
                    throw new DecoderException(
                           StringUtil.simpleClassName(getClass()) + ".decode() method must consume the inbound data " +
                           "or change its state if it decoded something.");
                }
                if (isSingleDecode()) {
                    break;
                }
            }
        } catch (DecoderException e) {
            throw e;
        } catch (Exception cause) {
            throw new DecoderException(cause);
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章