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<Object> out) throws Exception {
*
* if (buf.readableBytes() < 4) {
* return;
* }
*
* buf.markReaderIndex();
* int length = buf.readInt();
*
* if (buf.readableBytes() < length) {
* buf.resetReaderIndex();
* return;
* }
*
* out.add(buf.readBytes(length));
* }
* }
* </pre>
* 下面使用 {@link ReplayingDecoder} 簡化了實現:
* <pre>
* public class IntegerHeaderFrameDecoder
* extends {@link ReplayingDecoder}<{@link Void}> {
*
* 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}<{@link Void}> {
*
* private final Queue<Integer> values = new LinkedList<Integer>();
*
* {@code @Override}
* public void decode(.., {@link ByteBuf} buf, List<Object> 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}<{@link Void}> {
*
* private final Queue<Integer> values = new LinkedList<Integer>();
*
* {@code @Override}
* public void decode(.., {@link ByteBuf} buf, List<Object> 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}<<strong>MyDecoderState</strong>> {
*
* private int length;
*
* public IntegerHeaderFrameDecoder() {
* // 設置初始狀態.
* <strong>super(MyDecoderState.READ_LENGTH);</strong>
* }
*
* {@code @Override}
* protected void decode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf, List<Object> 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}<<strong>{@link Void}</strong>> {
*
* <strong>private boolean readLength;</strong>
* private int length;
*
* {@code @Override}
* protected void decode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf, List<Object> 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}<{@link Void}> {
*
* {@code @Override}
* protected void decode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf, List<Object> 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);
}
}
}