zoukankan      html  css  js  c++  java
  • Netty中解码基于分隔符的协议和基于长度的协议

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

    基于分隔符的协议

    基于分隔符的(delimited)消息协议使用定义的字符来标记的消息或者消息段(通常被称为帧)的开头或者结尾。由RFC文档正式定义的许多协议(如SMTP、POP3、IMAP以及Telnet名称)都是这样的。此外,当然,私有组织通常也拥有他们自己的专有格式。无论你使用什么样的协议,下面列出的解码器都能帮助你定义可以提取由任意标记(token)序列分隔的帧的自定义解码器。

    用于处理基于分隔符的协议和基于长度的协议的解码器

    DelimiterBasedFrameDecoder 使用任何由用户提供的分隔符来提取帧的通用解码器。
    LineBasedFrameDecoder 提取由行尾符( 或者 )分隔的帧的解码器。这个解码器比DelimiterBasedFrameDecoder更快。

    由行尾符分隔的帧图解析

    下面代码清单展示了如何使用LineBasedFrameDecoder来处理上图所示的场景。

    public class LineBasedHandlerInitializer extends ChannelInitializer<Channel>{
        @Override
        protected void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new LineBasedFrameDecoder(64 * 1024));// 该LineBasedFrameDecoder将提取的帧转发给下一个ChannelInboundHandler
            pipeline.addLast(new FrameHandler());// 添加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,只需要将特定的分隔符序列指定到其构造函数即可。

    这些解码器是实现你自己的基于分隔符的协议的工具。作为示例,我们将使用下面的协议规范:

    ①传入数据流是一系列的帧,每个帧都由换行符( )分隔;

    ②每个帧都由一系列的元素组成,每个元素都由单个空格字符分隔;

    ③一个帧的内容代表一个命令,定义为一个命令名称后跟着数目可变的参数。

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

    ①Cmd—将帧(命令)的内容存储在ByteBuf 中,一个ByteBuf 用于名称,另一个用于参数;

    ②CmdDecoder—从被重写了的decode()方法中获取一行字符串,并从它的内容构建一个Cmd的实例;

    ③CmdHandler —从CmdDecoder 获取解码的Cmd 对象,并对它进行一些处理;

    ④CmdHandlerInitializer为了简便起见,我们将会把前面的这些类定义为专门的ChannelInitializer的嵌套类,其将会把这些ChannelInboundHandler安装到ChannelPipeline中。

    这个解码器的关键是扩展LineBasedFrameDecoder,详看如下代码 使用ChannelInitializer 安装解码器:

    public class CmdHandlerInitializer extends ChannelInitializer<Channel> {
        final byte SPACE = (byte) ' ';
    
        @Override
        protected void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new CmdDecoder(64 * 1024));// 添加CmdDecoder 以提取Cmd 对象,并将它转发给下一个ChannelInboundHandler
            pipeline.addLast(new CmdHandler());// 添加CmdHandler以接收和处理Cmd对象
        }
    
        public static final class Cmd {// Cmd POJO
            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 frame = (ByteBuf) super.decode(ctx, buffer);// 从ByteBuf中提取由行尾符序列分隔的帧
                if (frame == null) {
                    return null;// 如果输入中没有帧,则返回null
                }
                int index = frame.indexOf(frame.readerIndex(), // 查找第一个空格字符的索引。前面是命令名称,接着是参数
              frame.writerIndex(), SPACE);
    return new Cmd(frame.slice(frame.readerIndex(), index), // 使用包含有命令名称和参数的切片创建新的Cmd对象
              frame.slice(index + 1, frame.writerIndex())); } } public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> { @Override public void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception { // Do something with the command 处理传经ChannelPipeline的Cmd对象 } } }

    基于长度的协议

    基于长度的协议通过将它的长度编码到帧的头部来定义帧,而不是使用特殊的分隔符来标记它的结束。下面列出了Netty提供的用于处理这种类型的协议的两种解码器。

    用于基于长度的协议的解码器

    FixedLengthFrameDecoder 提取在调用构造函数时指定的定长帧
    LengthFieldBasedFrameDecoder 根据编码进帧头部中的长度值提取帧;该字段的偏移量以及长度在构造函数中指定

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

    你将经常会遇到被编码到消息头部的帧大小不是固定值的协议。为了处理这种变长帧,你可以使用LengthFieldBasedFrameDecoder,它将从头部字段确定帧长,然后从数据流中提取指定的字节数。
    下图展示了将变长帧大小编码进头部的消息的示例,其中长度字段在帧中的偏移量为0,并且长度为2字节。

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

    使用LengthFieldBasedFrameDecoder 解码器基于长度的协议

    public class LengthBasedInitializer extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new LengthFieldBasedFrameDecoder(64 * 1024, 0, 8));// 使用LengthFieldBasedFrameDecoder解码将帧长度编码到帧起始的前8个字节中的消息
            pipeline.addLast(new FrameHandler());// 添加FrameHandler以处理每个帧
        }
    
        public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
            @Override
            public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
                // Do something with the frame 处理帧的数据
            }
        }
    }

    现在我们知道了Netty提供的,用于支持那些通过指定协议帧的分隔符或者长度(固定的或者可变的)以定义字节流的结构的协议的编解码器。你将会发现这些编解码器的许多用途,因为许多的常见协议都落到了这些分类之一中。

  • 相关阅读:
    利用Continuous Testing实现Eclipse环境自动单元测试
    GWT-Dev-Plugin(即google web toolkit developer plugin)for Chrome的安装方法
    在SQL Server 2012中实现CDC for Oracle
    在SSIS 2012中使用CDC(数据变更捕获)
    SQL Server Data Tools – Business Intelligence for Visual Studio 2012安装时提示“The CPU architecture....”的解决方法
    SQL Server 2012新特性(1)T-SQL操作FileTable目录实例
    RHEL每天定时备份Oracle
    GWT-Dev-Plugin(即google web toolkit developer plugin)for firefox的下载地址
    Oracle中修改表名遇到“ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效”
    Oracle中序列(SEQUENCE)的使用一例
  • 原文地址:https://www.cnblogs.com/shamo89/p/8557889.html
Copyright © 2011-2022 走看看