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.

Table 11-5: Decoders for handling delimiter-based and length-based protocols

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

Figure 11-5: Frames separated by end-of-line characters

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

 package io.netty.example.cp11;
 ​
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.*;
 import io.netty.handler.codec.LineBasedFrameDecoder;
 ​
 /**
  * 11.8 处理由行尾符分隔的帧
  */
 public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
         //  LineBasedFrameDecoder将提取的帧转发给下一个 ChannelInboundHandler
         pipeline.addLast(new LineBasedFrameDecoder(64 * 1024));
         // 添加 FrameHandler以接收帧
         pipeline.addLast(new FrameHandler());
     }
 ​
     public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
 ​
         // 传入了单个帧的内容
         @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 (\n)
  • 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, store the content of the frame (command) in a ByteBuf, one ByteBuf for the name and another for the parameters
  • CmdDecoder, get a line of string from the overridden decode(), and build a Cmd instance from its content
  • CmdHandler, get the decoded Cmd object from CmdDecoder and do some processing on it
  • CmdHandlerInitializer, for simplicity, defines the previous classes as nested classes of specialized ChannelInitializers, which will install these ChannelInboundHandlers to ChannelPipeline

As you will see in Listing 11-9, the key to this decoder is extending LineBasedFrameDecoder

 package io.netty.example.cp11;
 ​
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.*;
 import io.netty.handler.codec.LineBasedFrameDecoder;
 ​
 /**
  * 11.9 使用 ChannelInitializer 安装解码器
  */
 public class CmdHandlerInitializer extends ChannelInitializer<Channel> {
 ​
     private static final byte SPACE = (byte) ' ';
 ​
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
         // 添加 CmdDecoder 以提取Cmd 对象,并将它转发给下一ChannelInboundHandler
         pipeline.addLast(new CmdDecoder(64 * 1024));
         // 添加 CmdHandler 以接收和处理 Cmd 对象
         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 {
             // 从 ByteBuf 中提取由行尾符序列分隔的帧
             ByteBuf frame = (ByteBuf) super.decode(ctx, buffer);
             // 如果输入中没有帧,则返回 null
             if (frame == null) {
                 return null;
             }
             // 查找第一个空格字符的索引。前面是命令名称,接着是参数
             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 {
 ​
             // 处理传经 ChannelPipeline 的 Cmd 对象
             // Do something with the command
         }
     }
 }
 ​

2 Length-Based Protocols

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:

Table 11-6: Decoders for length-based protocols

Figure 11-6 shows the function of FixedLengthFrameDecoder, which has specified a frame length of 8 bytes during construction:

Figure 11-6: Decoding a frame of length 8 bytes

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 field, and then extract the specified number of bytes from the data stream.

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

Figure 11-7: Message with variable length frame size encoded in header

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.

 package io.netty.example.cp11;
 ​
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.*;
 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
 ​
 /**
  * Listing 11.10 使用 LengthFieldBasedFrameDecoder 解码器基于长度的协议
  */
 public class LengthBasedInitializer extends ChannelInitializer<Channel> {
 ​
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
         // 使用 LengthFieldBasedFrameDecoder 解码将帧长度编码到帧起始的前 8 个字节中的消息
         pipeline.addLast(new LengthFieldBasedFrameDecoder(64 * 1024, 0, 8));
         // 添加 FrameHandler 以处理每个帧
         pipeline.addLast(new FrameHandler());
     }
 ​
     public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
 ​
         @Override
         public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
             // 处理帧的数据
         }
     }
 }

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

おすすめ

転載: juejin.im/post/7235274652729278525