我们需要区分不同帧的首尾,通常需要在结尾设定特定分隔符或者在首部添加长度字段,分别称为分隔符协议和基于长度的协议,本节讲解 Netty 如何解码这些协议。

一、分隔符协议

  Netty 附带的解码器可以很容易的提取一些序列分隔:  
图片描述
  下面显示了使用 “\r\n”分隔符的处理:  
图片描述
  下面为 LineBaseFrameDecoder 的简单实现:

 public class CmdHandlerInitializer extends ChannelInitializer<Channel> {
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
         // 添加解码器,
         pipeline.addLast(new CmdDecoder(65 * 1024));
         pipeline.addLast(new CmdHandler());
     }
 
     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;
         }
     }
     
     /**
      * 根据分隔符将消息解码成Cmd对象传给下一个处理器
      */
     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);
             if(frame == null)
                 return null;
             int index = frame.indexOf(frame.readerIndex(), frame.writerIndex(), (byte)' ');
             // 提取 Cmd 对象
             return new Cmd(frame.slice(frame.readerIndex(), index), 
                     frame.slice(index+1, frame.writerIndex()));
         }
     }
     
     public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> {
 
         @Override
         protected void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception {
             // 处理 Cmd 信息
         }
         
     }
 }

  上面的例子主要实现了利用换行符‘\n’分隔帧,然后将每行数据解码成一个 Cmd 实例。

二、基于长度的协议

  基于长度的协议在帧头定义了一个帧编码的长度,而不是在结束位置用一个特殊的分隔符来标记。Netty 提供了两种编码器,用于处理这种类型的协议,如下:
  
图片描述

  FixedLengthFrameDecoder 的操作是提取固定长度每帧 8 字节,如下图所示:
  
图片描述
  但大部分时候,我们会把帧的大小编码在头部,这种情况可以使用 LengthFieldBaseFrameDecoder,它会提取帧的长度并根据长度读取帧的数据部分,如下:  
图片描述
  下面是 LengthFieldBaseFrameDecoder 的一个简单应用:

 /**
  * 基于长度的协议
  * LengthFieldBasedFrameDecoder
  */
 public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
         // 用于提取基于帧编码长度8个字节的帧
         pipeline.addLast(new LengthFieldBasedFrameDecoder(65*1024, 0, 8));
         pipeline.addLast(new FrameHandler());
     }
     
     public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
 
         @Override
         protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
             // TODO 数据处理
         }
         
     }
 
 }

  上面的例子主要实现了提取帧首部 8 字节的长度,然后提取数据部分进行处理。