This is the 16th day of my participation in the August More Text Challenge. For details, see:August is more challenging

Introduction to the

In the previous series of articles, we mentioned that a channel in Netty only accepts objects of type ByteBuf. If it is not a ByteBuf object, it needs to be converted with encoders and decoders. Today, we will talk about the issues in implementing netty’s custom encoders and decoders.

Custom encoder and decoder implementation

Before introducing netty’s own encoder and decoder, tell everyone how to implement custom encoder and decoder.

All the encoder and decoder in the netty is a derivative ChannelInboundHandlerAdapter and ChannelOutboundHandlerAdapter.

For ChannelOutboundHandlerAdapter, the most important two classes is MessageToByteEncoder and MessageToMessageEncoder.

MessageToByteEncoder encodes messages into ByteBuf. This class is also the most commonly used class for custom encodings. We can directly inherit this class and implement encode methods. Notice that this class has a generic type that specifies the object type of the message.

For example, if we want to convert an Integer to a ByteBuf, we can write:

public class IntegerEncoder extends MessageToByteEncoder<Integer> { @Override public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception { out.writeInt(msg); }}Copy the code

MessageToMessageEncoder converts messages from message to message. Since messages cannot be written directly to a channel, MessageToByteEncoder is used together with MessageToByteEncoder.

Here is an example of an Integer to a String:

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

For ChannelInboundHandlerAdapter, the most important two classes is ByteToMessageDecoder and MessageToMessageDecoder.

ByteToMessageDecoder converts ByteBuf to the corresponding message type. We need to inherit this class and implement the decode method. Here is a decoder that reads all the readable bytes from ByteBuf and puts the result into a new ByteBuf.

public class SquareDecoder extends ByteToMessageDecoder { @Override public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { out.add(in.readBytes(in.readableBytes())); }}Copy the code

MessageToMessageDecoder is a conversion between messages. Once again, you just need to implement the decode method, as follows:

public class StringToIntegerDecoder extends MessageToMessageDecoder<String> { @Override public void decode(ChannelHandlerContext ctx, String message, List<Object> out) throws Exception { out.add(message.length()); }}Copy the code

ReplayingDecoder

The above code looks simple, but there are a few issues to note along the way.

For a Decoder, we read the data from the ByteBuf and then convert it. However, in the process of reading, the data changes in ByteBuf are not known. It is possible that the ByteBuf is not ready in the process of reading, so it is necessary to judge the size of the readable bytes in ByteBuf during reading.

We need to parse a data structure, for example, the first four bytes of data structure is an int, said behind the length of the byte array, we need to determine whether there is four bytes in the ByteBuf, then read this 4 bytes as the length of the byte array, and then read the length of the byte array, finally get to read the results, If there is a problem with one of these steps, or if there are not enough bytes to read, you need to return and wait for the next read. As follows:

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 order to solve this problem, Netty provides ReplayingDecoder to simplify the above operation. In the ReplayingDecoder, it is assumed that all ByteBuf is ready and can be read directly from the middle.

The above example is rewritten with the ReplayingDecoder as follows:

   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

It tries to read the byte and if it doesn’t, it throws an exception. When the ReplayingDecoder receives the exception, it calls decode again.

While ReplayingDecoder is simple to use, it has two problems.

The first problem is performance, because the decode method will be repeatedly called. If the ByteBuf itself does not change, it will result in the same ByteBuf being repeated by decode, resulting in a performance waste. The solution to this problem is to perform the decode process in stages. For example, in the example above, we need to read the length of the Byte array first, and then read the actual Byte array. After reading the sum of the byte array, you can call checkpoint() to create a savepoint. The next time you try decode, you can skip the savepoint and continue with the process as follows:

public enum MyDecoderState { READ_LENGTH, READ_CONTENT; } public class IntegerHeaderFrameDecoder extends ReplayingDecoder<MyDecoderState> { private int length; public IntegerHeaderFrameDecoder() { // Set the initial state. super(MyDecoderState.READ_LENGTH); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception { switch (state()) { case READ_LENGTH: length = buf.readInt(); checkpoint(MyDecoderState.READ_CONTENT); case READ_CONTENT: ByteBuf frame = buf.readBytes(length); checkpoint(MyDecoderState.READ_LENGTH); out.add(frame); break; default: throw new Error("Shouldn't reach here."); }}}Copy the code

The second problem is that the decode method of the same instance may be called multiple times. If we have a private variable in the ReplayingDecoder, we need to consider cleaning the private variable to avoid data contamination due to multiple calls.

conclusion

By inheriting from the above classes, we can implement the encoding and decoding logic ourselves. But there seems to be a bit of a problem, is the custom encoding and decoder too complex? You also need to determine the size of the byte array to read. Is there an easier way?

Yes, look forward to the next article in the Netty series: Encoders and decoders that come with Netty.

An example for this article is learn-Netty4

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

The most popular interpretation, the most profound dry goods, the most concise tutorial, many you do not know the small skills waiting for you to find!

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