为了账号安全,请及时绑定邮箱和手机立即绑定

Netty源码阅读入门实战(八)-解码(更新 ing)

标签:
Java

就像很多标准的架构模式都被各种专用框架所支持一样,常见的数据处理模式往往也是目标实现的很好的候选对象,它可以节省开发人员大量的时间和精力。
当然这也适应于本文的主题:编码和解码,或者数据从一种特定协议的格式到另一种格式的转 换。这些任务将由通常称为编解码器的组件来处理
Netty 提供了多种组件,简化了为了支持广泛 的协议而创建自定义的编解码器的过程
例如,如果你正在构建一个基于 Netty 的邮件服务器,那 么你将会发现 Netty 对于编解码器的支持对于实现 POP3、IMAP 和 SMTP 协议来说是多么的宝贵

0 什么是编解码器

每个网络应用程序都必须定义

  • 如何解析在两个节点之间来回传输的原始字节

  • 如何将其和目标应用程序的数据格式做相互转换

这种转换逻辑由编解码器处理,编解码器由编码器和解码器组成,它们每种都可以将字节流从一种格式转换为另一种格式

那么它们的区别是什么呢?
如果将消息看作是对于特定的应用程序具有具体含义的结构化的字节序列— 它的数据。那 么编码器是将消息转换为适合于传输的格式(最有可能的就是字节流);而对应的解码器则是将 网络字节流转换回应用程序的消息格式。因此,编码器操作出站数据,而解码器处理入站数据。
记住这些背景信息,接下来让我们研究一下 Netty 所提供的用于实现这两种组件的类。

1 Netty解码概述

5bc9dadf0001b45410000531.jpg

1.1 两个问题

5bc9dae00001855610000200.jpg


在这一节中,我们将研究 Netty 所提供的解码器类,这些类覆盖了两个不同的用例

  • 将字节解码为消息——ByteToMessageDecoder 和 ReplayingDecoder

  • 将一种消息类型解码为另一种——MessageToMessageDecoder

因为解码器是负责将入站数据从一种格式转换到另一种格式,所以知道 Netty 的解码器实
现了 ChannelInboundHandler 也不会让你感到意外
什么时候会用到解码器呢?很简单:每当需要为 ChannelPipeline 中的下一个 Channel- InboundHandler 转换入站数据时会用到
此外,得益于ChannelPipeline 的设计,可以将多个解码器连接在一起,以实现任意复杂的转换逻辑,这也是 Netty 是如何支持代码的模块化以及复用的一个很好的例子

5bc9dae000014d7410000710.jpg


5bc9dae100014cd410000404.jpg


5bc9dae200011aa810000505.jpg


2 抽象解码器ByteToMessageDecoder

2.1 示例

将字节解码为消息(或者另一个字节序列)是一项如此常见的任务,以至于 Netty 特地为它提供了一个抽象的基类:ByteToMessageDecoder
由于你不可能知道远程节点是否会一次性地发送一个完整的消息,所以这个类会对入站数据进行缓冲,直到它准备好处理

5bc9dae300011f9a10000324.jpg

ByteToMessageDecoderAPI


假设你接收了一个包含简单 int 的字节流,每个 int 都需要被单独处理
在这种情况下,你需要从入站ByteBuf中读取每个 int,并将它传递给ChannelPipeline 中的下一个 ChannelInboundHandler
为了解码这个字节流,你要扩展 ByteToMessageDecoder类(原子类型的 int 在被添加到 List 中时,会被自动装箱为 Integer)

5bc9dae5000157bd10000486.jpg

ToIntegerDecoder


每次从入站 ByteBuf 中读取 4 字节,将其解码为一个 int,然后将它添加到一个 List 中
当没有更多的元素可以被添加到该 List 中时,它的内容将会被发送给下一个 Channel- InboundHandler

5bc9dae5000152a910000235.jpg

ToIntegerDecoder类扩展了ByteToMessageDecoder


虽然ByteToMessageDecoder可以很简单地实现这种模式,但是你可能会发现,在调用 readInt()前不得不验证所输入的 ByteBuf 是否具有足够的数据有点繁琐
在下一节中, 我们将讨论 ReplayingDecoder,它是一个特殊的解码器,以少量的开销消除了这个步骤


2.2 源码解析

5bc9dae60001ccd810000591.jpg


5bc9dae60001ab0010000316.jpg

解码步骤

2.2.1 累加字节流

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        //基于 ByteBuf 进行解码的,如果不是直接将当前对象向下传播
        if (msg instanceof ByteBuf) {
            CodecOutputList out = CodecOutputList.newInstance();            try {
                ByteBuf data = (ByteBuf) msg;                //若当前累加器为空,说明是第一次从 IO 流中读取数据
                first = cumulation == null;                if (first) {                    //第一次会将累加器赋值为刚读进来的 ByteBuf 对象数据
                    cumulation = data;
                } else {                    //非第一次,则将当前累加器中的数据和读取进来的数据进行累加
                    cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
                }                //调用子类的解码方法去解析
                callDecode(ctx, cumulation, out);
            } catch (DecoderException e) {                throw e;
            } catch (Throwable t) {                throw new DecoderException(t);
            } finally {                if (cumulation != null && !cumulation.isReadable()) {
                    numReads = 0;
                    cumulation.release();
                    cumulation = null;
                } else if (++ numReads >= discardAfterReads) {                    // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                    // See https://github.com/netty/netty/issues/4275
                    numReads = 0;
                    discardSomeReadBytes();
                }                int size = out.size();
                decodeWasNull = !out.insertSinceRecycled();
                fireChannelRead(ctx, out, size);
                out.recycle();
            }
        } else {
            ctx.fireChannelRead(msg);
        }
    }

其中的cumulator

5bc9daf0000109f510000041.jpg


看一下这个MERGE_CUMULATOR


    public static final Cumulator MERGE_CUMULATOR = new Cumulator() {        @Override
        public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
            ByteBuf buffer;            //当前的写指针后移一定字节,若超过最大容量,则进行扩容
            if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
                    || cumulation.refCnt() > 1) {                // Expand cumulation (by replace it) when either there is not more room in the buffer
                // or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
                // duplicate().retain().
                //
                // See:
                // - https://github.com/netty/netty/issues/2327
                // - https://github.com/netty/netty/issues/1764
                buffer = expandCumulation(alloc, cumulation, in.readableBytes());
            } else {
                buffer = cumulation;
            }            //将当前数据写到累加器中
            buffer.writeBytes(in);            //将读进的数据对象释放
            in.release();            return buffer;
        }
    };

2.2.2 调用子类的 decode方法进行解析

5bc9daf1000166eb10000466.jpg

进入该方法查看源码

    protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {        try {            // 只要累加器有数据,循环就会继续执行下去
            while (in.isReadable()) {
                int outSize = out.size();                // 判断当前list 里是否已经有对象(首次执行时,肯定是不会运行此段代码的)
                if (outSize > 0) {                    // 有,则通过事件传播机制向下传播
                    fireChannelRead(ctx, out, outSize);
                    out.clear();                    // Check if this handler was removed before continuing with decoding.
                    // If it was removed, it is not safe to continue to operate on the buffer.
                    //
                    // See:
                    // - https://github.com/netty/netty/issues/4635
                    if (ctx.isRemoved()) {                        break;
                    }
                    outSize = 0;
                }                // 记录当前可读数据长度
                int oldInputLength = in.readableBytes();
                decode(ctx, in, out);                // Check if this handler was removed before continuing the loop.
                // If it was removed, it is not safe to continue to operate on the buffer.
                //
                // See https://github.com/netty/netty/issues/1664
                if (ctx.isRemoved()) {                    break;
                }                if (outSize == out.size()) {                    if (oldInputLength == in.readableBytes()) {                        break;
                    } else {                        continue;
                    }
                }                if (oldInputLength == in.readableBytes()) {                    throw new DecoderException(
                            StringUtil.simpleClassName(getClass()) +                            ".decode() did not read anything but decoded a message.");
                }                if (isSingleDecode()) {                    break;
                }
            }
        } catch (DecoderException e) {            throw e;
        } catch (Throwable cause) {            throw new DecoderException(cause);
        }
    }

编解码器中的引用计数

对于编码器和解码器来说:一旦消息被编码或者解码,它就会被 ReferenceCountUtil.release(message)调用自动释放
如果你需要保留引用以便稍后使用,那么你可以调用 ReferenceCountUtil.retain(message)这将会增加该引用计数,从而防止该消息被释放

3 基于固定长度解码器分析

4 行解码器分析

5 基于分隔符解码器分析

6 基于长度域解码器参数分析

7 基于长度域解码器分析

8 解码器总结



作者:芥末无疆sss
链接:https://www.jianshu.com/p/95c667b813bb
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消