Netty decodes delimiter-based and length-based protocols

In the process of using Netty, you will encounter delimiter and frame length based protocols that require decoders. Let’s look at the implementation provided by Netty to handle these scenarios.

1 Delimiter-based protocol

Delimited message protocols use defined characters to mark the beginning or end of a message or message segment (often called a frame). This is true of many protocols formally defined by RFC documents such as SMTP, POP3, IMAP, and Telnet.

Private organizations also often have their own proprietary formats. Regardless of the protocol you use, the decoders in Table 11-5 help you define custom decoders that can extract frames delimited by arbitrary sequences of tokens.

Figure 11-5 How frames are processed when they are separated by the end-of-line sequence \r\\
(carriage return + line feed):

Listing 11-8 shows how to use LineBasedFrameDecoder to process Figure 11-5:

scala copy code package io.netty.example.cp11;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.*;
 import io.netty.handler.codec.LineBasedFrameDecoder;
 
 /**
  * 11.8 Handling frames separated by end-of-line characters
  */
 public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch. pipeline();
         // LineBasedFrameDecoder forwards the extracted frame to the next ChannelInboundHandler
         pipeline.addLast(new LineBasedFrameDecoder(64 * 1024));
         // Add FrameHandler to receive frames
         pipeline. addLast(new FrameHandler());
     }
 
     public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
 
         // The content of a single frame is passed in
         @Override
         public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
             // Do something with the data extracted from the frame
         }
     }
 }

If you are using frames delimited by delimiters other than end-of-line characters, you can use DelimiterBasedFrameDecoder in a similar fashion by specifying a specific delimiter sequence to its constructor. These decoders are tools for implementing your own delimiter-based protocols. As an example, use the following protocol specification:

  • The incoming data stream is a series of frames, each separated by a newline character (\\
    )
  • Each frame consists of a sequence of elements, each separated by a single space character
  • The content of a frame represents a command, defined as a command name followed by a variable number of parameters

Our custom decoder for this protocol will define the following class:

  • Cmd, stores the contents of the frame (command) in a ByteBuf, one ByteBuf for the name and another for the arguments
  • CmdDecoder, which takes a line of string from the overridden decode() and builds a Cmd instance from its contents
  • CmdHandler, get the decoded Cmd object from CmdDecoder and do some processing on it
  • CmdHandlerInitializer, for simplicity, define the previous classes as nested classes of specialized ChannelInitializers, which will install these ChannelInboundHandlers to ChannelPipeline

As will be seen in Listing 11-9, the key to this decoder is the extension of LineBasedFrameDecoder

scala copy code package io.netty.example.cp11;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.*;
 import io.netty.handler.codec.LineBasedFrameDecoder;
 
 /**
  * 11.9 Use ChannelInitializer to install codecs
  */
 public class CmdHandlerInitializer extends ChannelInitializer<Channel> {
 
     private static final byte SPACE = (byte) ' ';
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch. pipeline();
         // Add CmdDecoder to extract Cmd object and forward it to next ChannelInboundHandler
         pipeline.addLast(new CmdDecoder(64 * 1024));
         // Add CmdHandler to receive and handle Cmd objects
         pipeline. addLast(new CmdHandler());
     }
 
     // Cmd POJO
     public static final class Cmd {
         private final ByteBuf name;
         private final ByteBuf args;
 
         public Cmd(ByteBuf name, ByteBuf args) {
             this.name = name;
             this.args = args;
         }
 
         public ByteBuf name() {
             return name;
         }
 
         public ByteBuf args() {
             return args;
         }
     }
 
     public static final class CmdDecoder extends LineBasedFrameDecoder {
 
         public CmdDecoder(int maxLength) {
             super(maxLength);
         }
 
         @Override
         protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
             // Extract frames separated by end-of-line sequences from ByteBuf
             ByteBuf frame = (ByteBuf) super. decode(ctx, buffer);
             // returns null if there are no frames in the input
             if (frame == null) {
                 return null;
             }
             // Find the index of the first space character. preceded by the command name, followed by the arguments
             int index = frame.indexOf(frame.readerIndex(), frame.writerIndex(), SPACE);
             return new Cmd(frame. slice(frame. readerIndex(), index), frame. slice(index + 1, frame. writerIndex()));
         }
     }
 
     public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> {
 
         @Override
         public void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception {
 
             // Process the Cmd object passed through the ChannelPipeline
             // Do something with the command
         }
     }
 }
 

2 Length-based protocol

Length-based protocols define a frame by encoding its length into the header of the frame, rather than using a special delimiter to mark its end (fixed frame size protocols, without encoding the frame length into the header).

Table 11-6 lists the two decoders provided by Netty for handling this type of protocol:

Figure 11-6 shows the function of FixedLengthFrameDecoder, which has specified a frame length of 8 bytes when it is constructed:

You will often come across protocols where the frame size encoded in the message header is not a fixed value. To handle such variable-length frames, a LengthFieldBasedFrameDecoder can be used, which will determine the frame length from the header fields, and then extract the specified number of bytes from the stream.

Figure 11-7, the offset of the length field in the frame is 0, and the length is 2 bytes:

LengthFieldBasedFrameDecoder provides several constructors to support various header configurations. Listing 11-10 shows how to use the constructor whose three constructor parameters are maxFrameLength, lengthFieldOffset, and lengthFieldLength. In this scenario, the frame length is encoded into the first 8 bytes of the frame start.

scala copy code package io.netty.example.cp11;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.*;
 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
 
 /**
  * Listing 11.10 Length-based protocol using LengthFieldBasedFrameDecoder decoder
  */
 public class LengthBasedInitializer extends ChannelInitializer<Channel> {
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch. pipeline();
         // Use LengthFieldBasedFrameDecoder to decode the message that encodes the frame length into the first 8 bytes of the frame start
         pipeline.addLast(new LengthFieldBasedFrameDecoder(64 * 1024, 0, 8));
         // Add FrameHandler to handle each frame
         pipeline. addLast(new FrameHandler());
     }
 
     public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
 
         @Override
         public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
             // process frame data
         }
     }
 }

See the codecs provided by Netty for supporting protocols that define the structure of the byte stream by specifying the delimiter or length (fixed or variable) of the protocol frame. You’ll find many uses for these codecs, as many common protocols fall into these classifications. (11-4) – Decode delimiter-based protocol and length-based protocol