Netty解码基于分隔符的协议和基于长度的协议

在使用 Netty 的过程中,你将会遇到需要解码器的基于分隔符和帧长度的协议。来看 Netty 所提供的用于处理这些场景的实现。

1 基于分隔符的协议

基于分隔符的(delimited)消息协议使用定义的字符来标记的消息或者消息段(通常被称为帧)的开头或者结尾。由RFC文档正式定义的许多协议(如SMTP、POP3、IMAP以及Telnet)都是这样的。

私有组织通常也有自己的专有格式。无论你使用什么样的协议,表11-5的解码器都能帮你定义可以提取由任意标记(token)序列分隔的帧的自定义解码器。

图 11-5 当帧由行尾序列\r\n(回车符+换行符)分隔时是如何被处理的:

代码11-8 展示如何使用 LineBasedFrameDecoder 处理图11-5:

 

scala

复制代码

 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         }     }  }

若你正在使用除了行尾符之外的分隔符分隔的帧,那你可以类似方式使用 DelimiterBasedFrameDecoder,将特定分隔符序列指定到其构造函数即可。这些解码器是实现你自己的基于分隔符的协议的工具。作为示例,使用如下协议规范:

  • 传入数据流是一系列的帧,每个帧都由换行符(\n)分隔
  • 每个帧都由一系列的元素组成,每个元素都由单个空格字符分隔
  • 一个帧的内容代表一个命令,定义为一个命令名称后跟着数目可变的参数

我们用于这个协议的自定义解码器将定义如下类:

  • Cmd,将帧(命令)的内容存储在 ByteBuf,一个 ByteBuf 用于名称,另一个用于参数
  • CmdDecoder,从被重写了的 decode() 中获取一行字符串,并从它的内容构建一个 Cmd 的实例
  • CmdHandler,从 CmdDecoder 获取解码的 Cmd 对象,并对它做一些处理
  • CmdHandlerInitializer,为了简便起见,把前面的这些类定义为专门的 ChannelInitializer 的嵌套类,其将会把这些 ChannelInboundHandler 安装到 ChannelPipeline

正如将在代码清单 11-9 中所能看到的那样,这个解码器的关键是扩展 LineBasedFrameDecoder

 

scala

复制代码

 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 基于长度的协议

基于长度的协议通过将它的长度编码到帧的头部来定义帧,而不是使用特殊的分隔符来标记它的结束(固定帧大小的协议,无需将帧长度编码到头部)。

表 11-6 列出Netty提供的用于处理这种类型的协议的两种解码器:

图 11-6 展示了 FixedLengthFrameDecoder 的功能,其在构造时已经指定了帧长度为 8 字节:

你将经常会遇到被编码到消息头部的帧大小不是固定值的协议。为了处理这种变长帧,可使用 LengthFieldBasedFrameDecoder,它将从头部字段确定帧长,然后从数据流中提取指定的字节数。

图11-7,长度字段在帧中的偏移量为0,且长度为2字节:

LengthFieldBasedFrameDecoder 提供几个构造器支持各种头部配置。代码11-10展示如何使用其 3 个构造参数分别为 maxFrameLength、lengthFieldOffset 和 lengthFieldLength 的构造函数。在这个场景中,帧的长度被编码到帧起始的前8字节。

 

scala

复制代码

 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 {              // 处理帧的数据         }     }  }

看到Netty 提供的用于支持那些通过指定协议帧的分隔符或长度(固定或可变的)以定义字节流的结构的协议的编解码器。你会发现这些编解码器的许多用途,因为许多常见协议都落到这些分类中。(11-4)-解码基于分隔符的协议和基于长度的协议

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值