您的位置:首页 > 教程 > 服务端类 > Golang > Netty分布式行解码器逻辑源码解析

Netty分布式行解码器逻辑源码解析

2022-03-29 12:01:26 来源:易采站长站 作者:

Netty分布式行解码器逻辑源码解析

目录
行解码器LineBasedFrameDecoder首先看其参数我们跟到重载的decode方法中我们看findEndOfLine(buffer)方法

前文传送门:Netty分布式固定长度解码器实现原理剖析

这一小节了解下行解码器LineBasedFrameDecoder, 行解码器的功能是一个字节流, 以\r\n或者直接以\n结尾进行解码, 也就是以换行符为分隔进行解析

同样, 这个解码器也继承了ByteToMessageDecoder

行解码器LineBasedFrameDecoder

首先看其参数

//数据包的最大长度, 超过该长度会进行丢弃模式
private final int maxLength;
//超出最大长度是否要抛出异常
private final boolean failFast;
//最终解析的数据包是否带有换行符
private final boolean stripDelimiter;
//为true说明当前解码过程为丢弃模式
private boolean discarding;
//丢弃了多少字节
private int discardedBytes;

其中的丢弃模式, 我们会在源码中看到其中的含义

我们看其decode方法

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方法和我们上一小节分析的decode方法一样, 调用重载的decode方法, 并将解码后的内容放到out集合中

我们跟到重载的decode方法中

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();
            //拿到分隔符长度, 如果是\r\n结尾, 分隔符长度为2
            final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
            //如果长度大于最大长度
            if (length > maxLength) {
                //指向换行符之后的可读字节(这段数据完全丢弃)
                buffer.readerIndex(eol + delimLength);
                //传播异常事件
                fail(ctx, length);
                return null;
            }
            //如果这次解析的数据是有效的
            //分隔符是否算在完整数据包里
            //true为丢弃分隔符
            if (stripDelimiter) {
                //截取有效长度
                frame = buffer.readRetainedSlice(length);
                //跳过分隔符的字节
                buffer.skipBytes(delimLength);
            } 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;
                //超过最大长度抛出异常
                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);
            //当前丢弃的字节为0
            discardedBytes = 0;
            //设置为未丢弃模式
            discarding = false;
            //丢弃完字节之后触发异常
            if (!failFast) {
                fail(ctx, length);
            }
        } else {
            //累计已丢弃的字节个数+当前可读的长度
            discardedBytes += buffer.readableBytes();
            //移动
            buffer.readerIndex(buffer.writerIndex());
        }
        return null;
    }
}

 final int eol = findEndOfLine(buffer) 

这里是找当前行的结尾的索引值, 也就是\r\n或者是\n:

6-3-1

图中不难看出, 如果是以\n结尾的, 返回的索引值是\n的索引值, 如果是\r\n结尾的, 返回的索引值是\r的索引值

我们看findEndOfLine(buffer)方法

private static int findEndOfLine(final ByteBuf buffer) {
    //找到/n这个字节
    int i = buffer.forEachByte(ByteProcessor.FIND_LF);
    //如果找到了, 并且前面的字符是-r, 则指向/r字节
    if (i > 0 && buffer.getByte(i - 1) == '\r') {
        i--;
    }
    return i;
}

这里通过一个forEachByte方法找\n这个字节, 如果找到了, 并且前面是\r, 则返回\r的索引, 否则返回\n的索引

回到重载的decode方法中:

 if (!discarding) 判断是否为非丢弃模式, 默认是就是非丢弃模式, 所以进入if中

 if (eol >= 0) 如果找到了换行符, 我们看非丢弃模式下找到换行符的相关逻辑:

final ByteBuf frame; 
final int length = eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1; 
if (length > maxLength) {
    buffer.readerIndex(eol + delimLength);
    fail(ctx, length);
    return null;
} 
if (stripDelimiter) { 
    frame = buffer.readRetainedSlice(length); 
    buffer.skipBytes(delimLength);
} else { 
    frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;

首先获得换行符到可读字节之间的长度, 然后拿到换行符的长度, 如果是\n结尾, 那么长度为1, 如果是\r结尾, 长度为2

 if (length > maxLength) 带表如果长度超过最大长度, 则直接通过 readerIndex(eol + delimLength) 这种方式, 将读指针指向换行符之后的字节, 说明换行符之前的字节需要完全丢弃

6-3-2

丢弃之后通过fail方法传播异常, 并返回null

继续往下看, 走到下一步, 说明解析出来的数据长度没有超过最大长度, 说明是有效数据包

 if (stripDelimiter) 表示是否要将分隔符放在完整数据包里面, 如果是true, 则说明要丢弃分隔符, 然后截取有效长度, 并跳过分隔符长度

将包含分隔符进行截取

以上就是非丢弃模式下找到换行符的相关逻辑

我们再看非丢弃模式下没有找到换行符的相关逻辑, 也就是非丢弃模式下,  if (eol >= 0) 中的else块:

final int length = buffer.readableBytes();
if (length > maxLength) { 
    discardedBytes = length; 
    buffer.readerIndex(buffer.writerIndex());
    discarding = true; 
    if (failFast) {
        fail(ctx, "over " + discardedBytes);
    }
} 
return null;

首先通过 final int length = buffer.readableBytes() 获取所有的可读字节数

然后判断可读字节数是否超过了最大值, 如果超过最大值, 则属性discardedBytes标记为这个长度, 代表这段内容要进行丢弃

6-3-3

 buffer.readerIndex(buffer.writerIndex()) 这里直接将读指针移动到写指针, 并且将discarding设置为true, 就是丢弃模式

如果可读字节没有超过最大长度, 则返回null, 表示什么都没解析出来, 等着下次解析

我们再看丢弃模式的处理逻辑, 也就是 if (!discarding) 中的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);
}

如果找到换行符, 则需要将换行符之前的数据全部丢弃掉

6-3-4

 final int length = discardedBytes + eol - buffer.readerIndex() 

这里获得丢弃的字节总数, 也就是之前丢弃的字节数+现在需要丢弃的字节数

然后计算换行符的长度, 如果是\n则是1, \r\n就是2

 buffer.readerIndex(eol + delimLength) 

这里将读指针移动到换行符之后的位置

然后将discarding设置为false, 表示当前是非丢弃状态

我们再看丢弃模式未找到换行符的情况, 也就是丢弃模式下,  if (eol >= 0) 中的else块:

discardedBytes += buffer.readableBytes();
buffer.readerIndex(buffer.writerIndex());

这里做的事情非常简单, 就是累计丢弃的字节数, 并将读指针移动到写指针, 也就是将数据全部丢弃

最后在丢弃模式下, decode方法返回null, 代表本次没有解析出任何数据

以上就是行解码器的相关逻辑

以上就是Netty分布式行解码器逻辑源码解析的详细内容,更多关于Netty分布式行解码器的资料请关注易采站长站其它相关文章!

如有侵权,请联系QQ:279390809 电话:15144810328

相关文章

  • golang用什么开发工具?

    golang用什么开发工具?

    golang用的开发工具有:1、Go Revive,是一个Go语言的代码质量检测工具;2、Go Callvis,可以用来可视化Go程序的调用图;3、Gaia,高效,快速,轻量级,并且对开发人员友好。golang用的开发工具有:1、Go Reviverevive 是一个 Go 语言的代码质量检测工具(Linter for Go),具有快速、可配置、可扩展、灵活和美观等特性,可作为 golint 的替
    2020-08-07
  • golang是多线程模式吗?

    golang是多线程模式吗?

    golang是多线程模式的,golang的线程模型是M P G模型,整体上Go程与内核线程是多对多对应的,因此首先来讲就一定是多线程的。golang是多线程模式。 由于gmp中的p与m是将p绑定与m内核线程上,而后p的最大数量有GOPROCESS确定,而M内核线程的数量会由go去限制为10K个,但是由于内核原因做不到这么多,所以这个限制就当做没有吧。拿个图明确一下Golang有些所谓的M比N模型,
    2020-08-07
  • golang用什么开发工具?

    golang用什么开发工具?

    golang用的开发工具有:1、Go Revive,是一个Go语言的代码质量检测工具;2、Go Callvis,可以用来可视化Go程序的调用图;3、Gaia,高效,快速,轻量级,并且对开发人员友好。golang用的开发工具有:1、Go Reviverevive 是一个 Go 语言的代码质量检测工具(Linter for Go),具有快速、可配置、可扩展、灵活和美观等特性,可作为 golint 的替
    2020-08-07
  • golang是单进程的吗?

    golang是单进程的吗?

    golang不是单进程的,而是多线程;golang的线程模型是M P G模型,整体上Go程与内核线程是多对多对应的,因此首先来讲就一定是多线程的。golang不是单进程的,而是多线程。Golang有些所谓的M比N模型,M个线程下可以创建N个go routine,一般而言N远大于M,本质上属于多线程模型,但是协程的调度由Go的runtime决定,强调开发者应该使用channel进行协程之间的同步。至
    2020-08-07
  • 代码详解使用Go基于WebSocket构建视频直播弹幕系统

    代码详解使用Go基于WebSocket构建视频直播弹幕系统

    (1)业务复杂度介绍开门见山,假设一个直播间同时500W人在线,那么1秒钟1000条弹幕,那么弹幕系统的推送频率就是: 500W * 1000条/秒=50亿条/秒 ,想想B站2019跨年晚会那次弹幕系统得是多么的NB,况且一个大型网站不可能只有一个直播间!使用Go做WebSocket开发无非就是三种情况:使用Go原生自带的库,也就是 golang.org/x/net ,但是这个官方库真是出了奇Bu
    2020-08-07
  • PHP语法和Go语法有什么差异?对比介绍

    PHP语法和Go语法有什么差异?对比介绍

    本篇文章给大家对比一下PHP语法和Go语法,带大家了解一下PHP语法和Go语法之间的差异。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。Go 是由 Google 设计的一门静态类型的编译型语言。它有点类似于 C,但是它包含了更多的优点,比如垃圾回收、内存安全、结构类型和并发性。它的并发机制使多核和网络机器能够发挥最大的作用。这是 GoLang 的最佳卖点之一。此外,Go 速度快,
    2020-08-07
  • golang需要什么基础?

    golang需要什么基础?

    golang需要什么基础?golang需要的基础是:Go语言语法特别简单简洁,有C的底子更好,差一些也没关系。前提是你要真心想学,才有足够的动力去学。1、初学Go语言首先弄懂基础语法和概念:基本数据类型、Struct、Array、map、Slice、指针、接口、map、内置函数,常用工具包等,还有接口和Slice的底层数据结构。这些不需要弄特别懂,能自己理解并自己描述我觉得就可以了,关键在实践和应
    2020-08-07
  • golang为什么那么火?

    golang为什么那么火?

    golang为什么那么火?golang那么火的原因:1, Concurrency的原生支持通过语言原生的Goroutine和Channel,很好的支持了Concurrency。你可以把Goroutine理解为非常轻量级的Thread。一个Goroutine只占用2KB的内存,但是一个Thread要占用1MB的内存。Goroutine的创建、销毁和切换的开销,相对于线程来说特别低。你可以随时起上千个
    2020-08-07