吃透Netty源码系列三十九之固定长度与换行解码器

FixedLengthFrameDecoder

这个比较简单,固定长度解码,只要到了这个长度,就切片这个长度的缓冲区当做一个消息。

public class FixedLengthFrameDecoder extends ByteToMessageDecoder {

    private final int frameLength;//固定一帧的长度


    public FixedLengthFrameDecoder(int frameLength) {
        checkPositive(frameLength, "frameLength");
        this.frameLength = frameLength;
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        if (in.readableBytes() < frameLength) {//如果可读字节的小于固定长度,什么都不做
            return null;
        } else {
            return in.readRetainedSlice(frameLength);//返回的是切片,会增加in引用计数,防止被回收了
        }
    }
}

LineBasedFrameDecoder

这个是换行符"\r\n"或者"\n"的分割,比前面那个稍微复杂点,因为还涉及到是否要分隔符,没读到分隔符要丢弃已读的数据,就算后面读到了分隔符,但是是属于前面丢弃的后半部分,所以也不要了。光说比较难懂,后面会画图解释的,先看一些属性,了解下可能会用的到。

属性

都有注释了,下面会结合方法说的。


    private final boolean failFast;//超过长度是否马上抛出异常,无论是不是把数据全读完了,true超过就抛,false读完整个数据后抛
    private final boolean stripDelimiter;//解码后的数据是否要去除分割符

    private boolean discarding;//是否正在丢弃输入数据
    private int discardedBytes;//丢弃的数据长度

    /** Last scan position. */
    private int offset;//最后一次扫描的索引位置

构造方法

一般就是传一个最大长度,其他默认就好。

  	public LineBasedFrameDecoder(final int maxLength) {
        this(maxLength, true, false);
    }
 	public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
        this.maxLength = maxLength;
        this.failFast = failFast;
        this.stripDelimiter = stripDelimiter;
    }

解码方法

//跟固定长度的那个一样
 @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }


decode内部方法

这个就有点复杂了,其实也还好啦就是4中情况。

  1. 上一次没有丢弃可读数据过的。如果找到分割符了,如果长度没超出,就根据是否要略过分隔符返回相应长度的切片,如果超出了就设置读索到分隔符之后并抛出异常。
  2. 上一次没有丢弃可读数据过的。如果没找到分隔符,长度又超过了最大长度就丢弃,设置丢弃数量,设置读索引到最后。根据需求抛出异常。
  3. 上一次有丢弃可读数据过的。如果找到分割符了,不处理,直接设置读索引到分隔符之后,因为这个是上一次丢弃的那部分所属的同一个消息的,都不要了。
  4. 上一次有丢弃可读数据过的。如果没有找到分割符了,继续丢弃,直接略过可读的数据。
  protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
        final int eol = findEndOfLine(buffer);
        if (!discarding) {//没有丢弃过可读的
            if (eol >= 0) {//找到分割符了
                final ByteBuf frame;
                final int length = eol - buffer.readerIndex();//
                final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;//如果是'\r',分割符是2个字节'\r\n',否则是一个'\n'

                if (length > maxLength) {//可读数据超过最大长度了,直接不要了
                    buffer.readerIndex(eol + delimLength);//设置读索引为分隔符索引之后
                    fail(ctx, length);//直接抛异常
                    return null;
                }

                if (stripDelimiter) {//如果略过分隔符的
                    frame = buffer.readRetainedSlice(length);//获取长度为length的切片
                    buffer.skipBytes(delimLength);//buffer略过分隔符
                } else {
                    frame = buffer.readRetainedSlice(length + delimLength);//包括分隔符在内的切片
                }

                return frame;
            } else {//没找到分割符,不会读取,不改变读索引
                final int length = buffer.readableBytes();
                if (length > maxLength) {//超过最大长度,也没找到分隔符
                    discardedBytes = length;//丢弃可读的
                    buffer.readerIndex(buffer.writerIndex());//直接略过可读的,设置为不可读
                    discarding = true;//有丢弃了
                    offset = 0;
                    if (failFast) {
                        fail(ctx, "over " + discardedBytes);
                    }
                }
                return null;
            }
        } else {
            if (eol >= 0) {//前面有丢弃的话,后面跟着的也不要了
                final int length = discardedBytes + eol - buffer.readerIndex();
                final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
                buffer.readerIndex(eol + delimLength);//直接略过前面的一部分了
                discardedBytes = 0;
                discarding = false;
                if (!failFast) {
                    fail(ctx, length);
                }
            } else {//还是没找到分隔符
                discardedBytes += buffer.readableBytes();//增加丢弃数量
                buffer.readerIndex(buffer.writerIndex());//直接略过可读的,设置为不可读
                // We skip everything in the buffer, we need to set the offset to 0 again.
                offset = 0;
            }
            return null;
        }
    }
findEndOfLine寻找换行分隔符的位置

可能是换行符"\r\n"或者"\n",所以位置会相差1,如果找到了索引offset 就从0开始,否则就从上一次长度的最后开始。

 private int findEndOfLine(final ByteBuf buffer) {
        int totalLength = buffer.readableBytes();
        int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
        if (i >= 0) {//找到了换行符
            offset = 0;
            if (i > 0 && buffer.getByte(i - 1) == '\r') {//如果索引不是0,且前一个是'\r',就返回前一个的索引
                i--;
            }
        } else {
            offset = totalLength;
        }
        return i;
    }
fail抛出异常
  private void fail(final ChannelHandlerContext ctx, int length) {
        fail(ctx, String.valueOf(length));
    }
    //超出长度报异常
    private void fail(final ChannelHandlerContext ctx, String length) {
        ctx.fireExceptionCaught(
                new TooLongFrameException(
                        "frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));
    }

图示举例

我还是来画图示意一下情况吧,黄色是我们要读取的数据,绿色的是被解码出的消息数据,我们设置编码器可以解码的最大长度是4,其他参数默认。
初始状态:
在这里插入图片描述
第一次读取,返回绿色的切片:
在这里插入图片描述
第二次读取的数据长度超出最大长度4了:
在这里插入图片描述
第三次读取最后两个,返回绿色切片:
在这里插入图片描述

分几个包的的情况

首先第一次接收"01",因为没遇到换行符,所以offset=2,返回null:
在这里插入图片描述
第二次接收"23\n2",返回绿色的切片:
在这里插入图片描述

分包丢弃的情况

第一次是这样,因为个数超过最大长度,所以会被丢弃,缓冲区也会被释放。
在这里插入图片描述
第二次收到后,发现是前面丢弃的另一部分,所以一起丢弃。
在这里插入图片描述
图可能也画的不是很好,还是结合代码分析吧。简单的就是说,如果第一次没找到换行符,而且可读长度超过最大长度了,后面无论多少数据,直到遇到换行符后,所有的数据都丢弃。只有在最大长度范围内,且有找到换行符的,才能拼起来返回切片。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
       在Java界,Netty无疑是开发网络应用的拿手菜。你不需要太多关注复杂的NIO模型和底层网络的细节,使用其丰富的接口,可以很容易的实现复杂的通讯功能。 本课程准备的十二个实例,按照由简单到复杂的学习路线,让你能够快速学习如何利用Netty来开发网络通信应用。                每个实例简洁、清爽、实用,重点在“用”上,即培训大家如何熟练的使用Netty解决实际问题,抛弃以往边讲应用边分析源码的培训模式所带来的“高不成低不就”情况,在已经能够熟练使用、并且清楚开发流程的基础上再去分析源码就会思路清晰、事半功倍。        本套课程的十二个实例,各自独立,同时又层层递进,每个实例都是针对典型的实际应用场景,学了马上就能应用到实际项目中去。 学习好Netty 总有一个理由给你惊喜!! 一、应用场景        Netty已经众多领域得到大规模应用,这些领域包括:物联网领域、互联网领域、电信领域、大数据领域、游戏行业、企业应用、银行证券金融领域、。。。  二、技术深度        多款开源框架中应用了Netty,如阿里分布式服务框架 Dubbo 的 RPC 框架、淘宝的消息中间件 R0cketMQ、Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架、开源集群运算框架 Spark、分布式计算框架 Storm、并发应用和分布式应用 Akka、。。。  三、就业前景         很多大厂在招聘高级/资深Java工程师时要求熟练学习、或熟悉Netty,下面只是简要列出,这个名单可以很长。。。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值