Make writing a habit together! This is the sixth day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

Introduction to the

Netty provides a decoder from ByteBuf to user-defined message called ByteToMessageDecoder. To use this decoder, we need to inherit the decoder and implement the decode method. This method converts the contents of ByteBuf to user-defined Message objects.

So what problems will be encountered in the process of using ByteToMessageDecoder? Why is there a ReplayingDecoder? Let’s take a look with that in mind.

ByteToMessageDecoder may encounter problems

ByteBuf decoder to decode decode decode decode decode decode decode decode decode decode decode decode decode decode decode decode decode

     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception 
Copy the code

In the input parameter, buf is the ByteBuf to be decoded, and out is the list of decoded objects. We need to convert the data in ByteBuf into our own objects and add them into the list of out.

There may be a problem here, because the data in BUF may not be ready when we call decode. For example, we need an Integer, but the data in BUF is not an Integer. This requires some judgment of the data logic in BUF. Let’s describe this process as a BUF object with a message length.

A Buf object with message length means that the first four bits of a Buf message form an integer that represents the length of subsequent messages in the Buf.

So the process of reading the message for transformation is that we read the first four bytes, get the length of the message, and then read the length of the message, and that’s what we really want to get.

How to implement this logic if it is inherited from ByteToMessageDecoder?

public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, 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)); }}Copy the code

In decode, we first need to determine if there are four bytes readable in buF, and return if not. If yes, the system reads the four bytes and determines whether the number of readable bytes in the BUF is smaller than the number that should be read. If the number is smaller, the data is not ready and needs to be reset by calling resetReaderIndex.

Finally, if all the conditions are met, the actual reading begins.

Is there a way to read the BUF exactly as you want without making a judgment beforehand? The answer is ReplayingDecoder.

Let’s take a look at the example above rewritten with ReplayingDecoder:

   public class IntegerHeaderFrameDecoder
        extends ReplayingDecoder<Void> {
  
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception {
  
       out.add(buf.readBytes(buf.readInt()));
     }
   }
Copy the code

With ReplayingDecoder, we can ignore whether buF has received enough readable data and just read it.

ReplayingDecoder is very simple in comparison. Next, let’s explore how ReplayingDecoder works.

The realization principle of ReplayingDecoder

ReplayingDecoder is actually a subclass of ByteToMessageDecoder, which is defined as follows:

public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder 
Copy the code

In ByteToMessageDecoder, the most important method is channelRead, in this method actually called callDecode(CTX, cumulation, out); To achieve cumulation to OUT decoding process.

The secret of ReplayingDecoder is to rewrite this method. Let’s look at the concrete implementation of this method:

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(); if (ctx.isRemoved()) { break; } outSize = 0; } S oldState = state; int oldInputLength = in.readableBytes(); try { decodeRemovalReentryProtection(ctx, replayable, out); 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 { continue; } } } catch (Signal replay) { replay.expect(REPLAY); if (ctx.isRemoved()) { break; } // Return to the checkpoint (or oldPosition) and retry. int checkpoint = this.checkpoint; if (checkpoint >= 0) { in.readerIndex(checkpoint); } else { } 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); }}Copy the code

The difference between ByteToMessageDecoder and ReplayingDecoder is that there is a checkpoint defined in ReplayingDecoder. This checkpint is set at the beginning of the attempt to decode data:

int oldReaderIndex = checkpoint = in.readerIndex();
Copy the code

If an exception occurs during decoding, checkpoint is used to reset index:

    int checkpoint = this.checkpoint;
         if (checkpoint >= 0) {
            in.readerIndex(checkpoint);
        } else {
    }
Copy the code

The exception caught here is Signal. What is Signal?

Signal is an Error object:

public final class Signal extends Error implements Constant<Signal> 
Copy the code

This exception is thrown from replayable.

Replayable is a unique ByteBuf object called ReplayingDecoderByteBuf:

final class ReplayingDecoderByteBuf extends ByteBuf
Copy the code

The Signal property is defined in ReplayingDecoderByteBuf:

    private static final Signal REPLAY = ReplayingDecoder.REPLAY;
Copy the code

This Signal exception is thrown from the get method in ReplayingDecoderByteBuf. Using getInt as an example, see how the exception is thrown:

    public int getInt(int index) {
        checkIndex(index, 4);
        return buffer.getInt(index);
    }

Copy the code

The getInt method first calls the checkIndex method to check the length of the buff. If it is smaller than the length to be read, the getInt method will throw an exception.

private void checkIndex(int index, int length) { if (index + length > buffer.writerIndex()) { throw REPLAY; }}Copy the code

This is where the Signal exception comes in.

conclusion

The above is the introduction of ReplayingDecoder, although ReplayingDecoder is good, but it can be seen from its implementation, ReplayingDecoder is by throwing exceptions to constantly retry, so in some special cases will cause performance degradation.

That is to say, while reducing the amount of our code, it also reduces the execution efficiency of the program. It seemed impossible to make a horse run and not eat grass.

This article is available at www.flydean.com/14-4-netty-…

The most popular interpretation, the most profound dry goods, the most concise tutorial, many tips you didn’t know waiting for you to discover!

Welcome to pay attention to my public number: “procedures those things”, understand technology, more understand you!