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

  • 相关阅读:
    PAT顶级 1024 Currency Exchange Centers (35分)(最小生成树)
    Codeforces 1282B2 K for the Price of One (Hard Version)
    1023 Have Fun with Numbers (20)
    1005 Spell It Right (20)
    1092 To Buy or Not to Buy (20)
    1118 Birds in Forest (25)
    1130 Infix Expression (25)
    1085 Perfect Sequence (25)
    1109 Group Photo (25)
    1073 Scientific Notation (20)
  • 原文地址:https://www.cnblogs.com/shamo89/p/8557889.html
Copyright © 2011-2022 走看看