学习“闪电侠”的Netty系列源码博文笔记

1、netty的reactor线程在添加一个任务的时候被创建,该线程实体为 FastThreadLocalThread,最后线程执行主体为NioEventLoop的run方法。

 

2、reactor线程大概做的事情分为对三个步骤不断循环

 1).首先轮询注册到reactor线程对用的selector上的所有的channel的IO事件

 2).处理产生网络IO事件的channel

 3).处理任务队列

 

3、总结reactor线程select步骤做的事情:不断地轮询是否有IO事件发生,并且在轮询的过程中不断检查是否有定时任务和普通任务,保证了netty的任务队列中的任务得到有效执行,轮询过程顺带用一个计数器避开了了jdk空轮询的bug,过程清晰明了

 

4、netty的reactor线程第二步做的事情为处理IO事件,netty使用数组替换掉jdk原生的HashSet来保证IO事件的高效处理,

每个SelectionKey上绑定了netty类AbstractChannel对象作为attachment,在处理每个SelectionKey的时候,就可以找到AbstractChannel,

然后通过pipeline的方式将处理串行到ChannelHandler,回调到用户方法

 

。对于每个NioEventLoop而言,

每隔256个channel从selector上移除的时候,将selectedKeys的内部数组全部清空,方便被jvm垃圾回收,

然后重新调用selectAgain重新填装一下 selectionKey,或许是保证现存的SelectionKey及时有效。

 

5、我们发现 taskQueue在NioEventLoop中默认是mpsc队列,mpsc队列,即多生产者单消费者队列,netty使用mpsc,方便的将外部线程的task聚集,

在reactor线程内部用单线程来串行执行,我们可以借鉴netty的任务执行模式来处理类似多线程数据上报,定时聚合的应用。

6、reactor执行task的所有逻辑,可以拆解成下面几个步骤

 1).从scheduledTaskQueue转移定时任务到taskQueue(mpsc queue)

 2).计算本次任务循环的截止时间

 3).执行任务

 4).收尾

 

7、netty执行task任务的步骤

 1).当前reactor线程调用当前eventLoop执行任务,直接执行,否则,添加到任务队列稍后执行

 2).netty内部的任务分为普通任务和定时任务,分别落地到MpscQueue和PriorityQueue

 3).netty每次执行任务循环之前,会将已经到期的定时任务从PriorityQueue转移到MpscQueue

 4).

netty每隔64个任务检查一下是否该退出任务循环

 

8、用户调用方法 Bootstrap.bind(port) 第一步就是通过反射的方式new一个NioServerSocketChannel对象,

并且在new的过程中创建了一系列的核心组件,包括ChannelId、ChannelConfig、Unsafe、Pipeline、ChannelHander等,仅此而已,并无他。

我们发现其实init也没有启动服务,只是初始化了一些基本的配置和属性,以及在pipeline上加入了一个接入器ServerBootstrapAcceptor,专门用来接受新连接。

 1).设置启动类参数,最重要的就是设置channel

2).创建server对应的channel,创建各大组件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等

3).初始化server对应的channel,设置一些attr,option,以及设置子channel的attr,option,给server的channel添加

     新channel接入器ServerBootstrapAcceptor,并出发addHandler,register等事件

4).调用到jdk底层做端口绑定,并触发active事件,active触发的时候,真正做服务端口绑定。

9、Channel继承关系简化图

Channel继承关系简化图

1).channel 继承 Comparable 表示channel是一个可以比较的对象
2).channel 继承AttributeMap表示channel是可以绑定属性的对象,在用户代码中,我们经常使用channel.attr(...)方法就是来源于此
3).ChannelOutboundInvoker是4.1.x版本新加的抽象,表示一条channel可以进行的操作
4).DefaultAttributeMap用于AttributeMap抽象的默认方法,后面channel继承了直接使用
5).AbstractChannel用于实现channel的大部分方法,其中我们最熟悉的就是其构造函数中,创建出一条channel的基本组件
6).AbstractNioChannel基于AbstractChannel做了nio相关的一些操作,保存jdk底层的 SelectableChannel,并且在构造函数中设置channel为非阻塞
7).最后,就是两大channel,NioServerSocketChannel,NioSocketChannel对应着服务端接受新连接过程和新连接读写过程

 

10、EventExecutorChooser

从worker reactor线程组中选择一个reactor线程,默认情况下,chooser通过 DefaultEventExecutorChooserFactory被创建,在创建reactor线程选择器的时候,会判断reactor线程的个数,如果是2的幂,就创建PowerOfTowEventExecutorChooser,否则,创建GenericEventExecutorChooser;

两种类型的选择器在选择reactor线程的时候,都是通过Round-Robin的方式选择reactor线程,唯一不同的是,PowerOfTowEventExecutorChooser是通过与运算,而GenericEventExecutorChooser是通过取余运算,与运算的效率要高于求余运算,可见,netty为了效率优化简直丧心病狂。

 

11、新连接接入

1).boos reactor线程轮询到有新的连接进入
2).通过封装jdk底层的channel创建 NioSocketChannel以及一系列的netty核心组件
3).将该条连接通过chooser,选择一条worker reactor线程绑定上去
4).注册读事件,开始新连接的读写

 

12、channel中的核心组件

channel中的核心组件

pipeline中的每个节点是一个ChannelHandlerContext对象,每个context节点保存了它包裹的执行器 ChannelHandler 执行操作所需要的上下文,其实就是pipeline,因为pipeline包含了channel的引用,可以拿到所有的context信息。

 

13、一段常见的客户端代码

bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
         p.addLast(new Spliter())
         p.addLast(new Decoder());
         p.addLast(new BusinessHandler())
         p.addLast(new Encoder());
     }
});

此时整个pipeline结构如下:

用两种颜色区分了一下pipeline中两种不同类型的节点,一个是 ChannelInboundHandler,处理inBound事件,最典型的就是读取数据流,加工处理;还有一种类型的Handler是 ChannelOutboundHandler, 处理outBound事件,比如当调用writeAndFlush()类方法时,就会经过该种类型的handler;

不管是哪种类型的handler,其外层对象 ChannelHandlerContext 之间都是通过双向链表连接,而区分一个 ChannelHandlerContext到底是in还是out,在添加节点的时候我们就可以看到netty是怎么处理的。

 

 

14、pipeline

1).以新连接创建为例,新连接创建的过程中创建channel,而在创建channel的过程中创建了该channel对应的pipeline,创建完pipeline之后,自动给该pipeline添加了两个节点,即ChannelHandlerContext,ChannelHandlerContext中有用pipeline和channel所有的上下文信息。

2).pipeline是双向个链表结构,添加和删除节点均只需要调整链表结构

3).pipeline中的每个节点包着具体的处理器ChannelHandler,节点根据ChannelHandler的类型是ChannelInboundHandler还是ChannelOutboundHandler来判断该节点属于in还是out或者两者都是

15、unsfae继承结构

unsfae继承结构

16、pipeline双向链表事件传播

1).一个Channel对应一个Unsafe,Unsafe处理底层操作,NioServerSocketChannel对应NioMessageUnsafe, NioSocketChannel对应NioByteUnsafe;
2).inBound事件从head节点传播到tail节点,outBound事件从tail节点     传播到head节点;
3).异常传播只会往后传播,而且不分inbound还是outbound节点,不像    outBound事件一样会往前传播。

17、写出缓存ChannelOutboundBuffer 数据结构

ChannelOutboundBuffer 里面的数据结构是一个单链表结构,每个节点是一个 EntryEntry 里面包含了待写出ByteBuf 以及消息回调 promise,下面分别是三个指针的作用

  1. .flushedEntry 指针表示第一个被写到操作系统Socket缓冲区中的节点
    2).unFlushedEntry 指针表示第一个未被写入到操作系统Socket缓冲区中的节点
    3).tailEntry指针表示ChannelOutboundBuffer缓冲区的最后一个节点
  1. 写出数据总结

1).pipeline中的编码器原理是创建一个ByteBuf,将java对象转换为ByteBuf,然后再把ByteBuf继续向前传递
2).调用write方法并没有将数据写到Socket缓冲区中,而是写到了一个单向链表的数据结构中,flush才是真正的写出
3).writeAndFlush等价于先将数据写到netty的缓冲区,再将netty缓冲区中的数据写到Socket缓冲区中,写的过程与并发编程类似,用自旋锁保证写成功
4).netty中的缓冲区中的ByteBuf为DirectByteBuf

 

 

19、LengthFieldBasedFrameDecoder

参数解释:

1).第一个参数是 maxFrameLength 表示的是包的最大长度,超出包的最大长度netty将会做一些特殊处理,后面会讲到
2).第二个参数指的是长度域的偏移量lengthFieldOffset

3).第三个参数指的是长度域长度lengthFieldLength

4).第四个参数指的是包体长度大小调整lengthAdjustment,如果长度域中保存的长度不包括某段H,但最后结果需要H,则需要加上H的长度,此时lengthAdjustment即为H的长度,相反如果需要丢掉某段,在该参数可为负数
5).第五个参数initialBytesToStrip,表示netty拿到一个完整的数据包之后向业务解码器传递之前,应该跳过多少字节,即为最后一步需要忽略的长度。

处理过程:

1).如果你使用了netty,并且二进制协议是基于长度,考虑使用LengthFieldBasedFrameDecoder吧,通过调整各种参数,一定会满足你的需求
2).LengthFieldBasedFrameDecoder的拆包包括合法参数校验,异常包处理,以及最后调用 ByteBuf 的retainedSlice来实现无内存copy的拆包

参考博文地址:闪电侠

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
嵌入式开发工程师必备的串口网络调试助手 ●本软件主要功能如下: 1、串口,TCP客户端/服务端,UDP客户端/服务端通讯调试 2、支持在发送数据包尾部指定位置添加CRC(ModbusCRC16、CCITT-CRC16、XOR、SUM、CRC32) 3、通过菜单或快捷键(Ctrl+F)可以调出查找控件,方便用户查找指定字符 4、在接收框中按下F3或Shift + F3,实现向下或向上快速查找指定字符 5、打开宏按钮可以打开多条数据发送窗口,可以支持无限条自定义数据 6、多数据发送窗口还可以切换到精简模式,不占用用户调试界面 7、接收的HEX数据可通过右键菜单计算对应的浮点数或ASCII码 8、支持串口超级终端模式,完全兼容XP系统超级终端按键字符 9、支持自动应答模式,通过配置文件可以设置需要软件自动应答的数据 10、支持切换时间戳显示模式(距开始抓包的秒数或距上一包的秒数) 11、通过发送菜单选项可以将发送框改为多行文本发送,还可定义回车字符 12、单行发送框支持20条历史发送数据记录,方便用户快速选择发送文本 13、支持分包超时设置,可以有效解决接收一帧数据与时间戳断开的问题 ●下期开发计划: 1、串口支持XModem、YModem、ZModem协议的文件传输 2、网络支持TFTP协议客户端和服务端文件传输 3、加入数据解析功能,可以从接收数据中摘取用户想要的数据做分析 4、网络通讯按协议筛选出数据 以上信息软件开启时默认显示,如果您下次不想它再提示,可以在显示菜单中关闭提示信息,软件右上角的定时帮助小提示也会一起关闭 更多详细功能请查看帮助内容,如果本软件对您有帮助,请通过捐赠方式鼓励一下作者^_^,您的鼓励将是作者不断完善、持续更新的动力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值