netty源码分析

nio的定式api:

Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(1233);
ssc.configureBlocking(false);
ssc.bind(address);
ssc.register(selector, SelectionKey.OP_ACCEPT);
for (;;) {
    int select = selector.select();
    if (select > 0) {
        Iterator iterator = selector.selectedKeys().iterator();
        while (iterator.hasNext()) {
            SelectionKey selectionKey = iterator.next();
            iterator.remove();
            if (selectionKey.isAcceptable()) {
                SocketChannel channel = ssc.accept();
                channel.configureBlocking(false);
                channel.register(selector,SelectionKey.OP_READ);
            }
            if (selectionKey.isReadable()) {
                ByteBuffer buffer = ByteBuffer.allocate(10);
                SocketChannel channel = (SocketChannel)selectionKey.channel();
                // 读取channel数据。。
                channel.close();
            }
        }
    }
}

nio的源码解析涉及到系统层面和c代码的调用在,这里不做深入讨论,目前只需要记住nio的使用api定式即可

源码分析

netty就是基于nio做的一次优秀的封装。

首先注意nio的几个重要组件:Selector(事件选择器),Channel(通信通道),Buffer(通信载体)

再来分析一下netty的几个重要组件:

EventLoop(线程组件),

Channel(通信通道,netty做了一层封装),

Pipeline(消息通知链),

ChannelHandler(真正触发事件干活的),

ChannelHandlerContext(对handler做了一层包装)

以上对netty的几个重要组件做了概括性的简单介绍,下面将逐个组件一一介绍,最后是把整个netty的启动和接收请求串起来

EventLoop:netty的最最核心的组件,提供了包括线程池技术,请求触发等待功能,可以说是netty整个功能的核心组件

接口层描述:Will handle all the I/O operations for a {@link Channel} once registered(会处理注册到上面的所有的io通信通道事件)

netty源码分析_第1张图片

 这是EventLoop的接口层面描述,我们在使用得最多的是NioEventLoop,首先看一下他的类继承图:

netty源码分析_第2张图片

 从名字看可知,该组件做的大部分和线程处理相关的事情

构造器:

netty源码分析_第3张图片

 在NioEventLoop的构造器中,我们看到了熟悉的Selector,说明NioEventLoop是持有了Selector组件的,其次可以发现,NioEventLoop里面有个很长的方法:

netty源码分析_第4张图片

里面有涉及到关于nio相关的事件类型(SelectionKey.OP_READ等等)的逻辑 入口:

netty源码分析_第5张图片

 netty的channel

首先看一下netty的channel通信通道定义

netty源码分析_第6张图片

然后我们看一下使用得最多的netty的通信通道的类图 

netty源码分析_第7张图片

 在nio中channel做为通信通道,做了2个动作。1 绑定端口号 2 用作通信通道,完成读,写,在netty的Channel中只是做了继承封装,相信也会有这2个动作的触发

构造器:

netty源码分析_第8张图片

 继续追踪构造器,最终在netty的顶层通信通道中发现如下逻辑:

netty源码分析_第9张图片

 可以发现,在NioServerSocketChannel(NioSocketChannel一样)中会由顶层抽象类构建pipeLine

换句话说:netty的channel会持有pipeLine对象

PipeLine:netty的消息推送链

接口定义

netty源码分析_第10张图片

netty的pipeLine接口定义如上,可以看到,pipeLine有继承三个接口,包括:ChannelInboundInvoker,ChannelOutboundInvoker,迭代器

其中两个Invoker先简单的理解成ChannleInbound和Channeloutboun的包装类

构造器:

在netty中pipeLine的默认实现:DefaultChannelPipeline

 netty源码分析_第11张图片

可以看到如下信息:

channel和PipeLine是相互持有

在pipeLine中有一个由ChannelHandlerContext(TailContext,HeadContext均是ChannelHandlerContext的实现内部类)组成的双向链表

 #addLast(ChannelHandler)

 最终会调用如下代码:

netty源码分析_第12张图片

 如上,在添加handler时,会首先把handler构建成ChannelHandlerContext,然后添加到pipeLine的双向链路尾部(这个是后续触发读写的数据基础)

ChannelHandlerContext

接口定义

netty源码分析_第13张图片

 由接口的定义说明可知:一个Context肯定有一个handler与之对应,换言之:ChannelHandlerContext里面会包含一个Handler

构造器:

在netty中ChannelHandlerContext的默认实现是DefaultChannelHandlerContext

netty源码分析_第14张图片

由构造器可知的消息:Context持有一个Handler,且持有一个PipeLine对象(在介绍Pipeline时,context会被加入到双向链表中)

 重要方法

DefaultChannelHandlerContext的父类:AbstractChannelContext中包含了很多关于fire方法,这些是和netty整个事件驱动模型有关的方法。举其中一个关于读的方法举例:

#invokeChannelRead(Context,msg);

#invokeChannelRead(msg);

ChannelHandler

netty源码分析_第15张图片在博客开头的关于那段注释:处理具体的消息。如果自己抽象出来是不是也是和ChannelHandler的功能差不多?相信netty的开发者的初衷也是如此

 在netty中,有2类handler,InboundHandler和OutboundHandler,

Inbound是处理关于读事件的,Outbound是处理关于写事件的

相信通过以上关于netty各组件的分析,对各组件功能已经有了个大概了解,下面分析一下netty整个启动过程

api定式

netty源码分析_第16张图片

 EventLoopGroup   vs  EventLoop

在前面的组件介绍中,有介绍过EventLoop,而EventLoopGroup可以简单粗暴的理解成就是一组EventLoop,不信?咱们看看EventLoopGroup源码

EventLoopGroup接口定义:

netty源码分析_第17张图片

 NioEventLoopGroup的类图如下:

netty源码分析_第18张图片

 构造器:

追中NioEventLoopGroup的构造器,在MutithreadEventExecutorGroup中会构造一个EventExecutor数组(EventLoop是EventExecutor的子接口)

netty源码分析_第19张图片

 EventExecutor数据在初始化时,最终会构造:NioEventLoop

netty源码分析_第20张图片

 在上面组件介绍时,已经说过了,NioEventLoop里面有包含Selector和循环选择处理逻辑,现在关注的问题是:何时触发了循环select逻辑,即:在NioEventLoop中什么时候触发了方法:

netty源码分析_第21张图片

 netty源码分析_第22张图片

灵魂入口 Bootstrap.bind()

既然bootstrap.bind是核心方法,那这个方法到底做了什么处理逻辑? 

netty源码分析_第23张图片

 看来,核心逻辑在initAndRegister(),继续跟踪

netty源码分析_第24张图片

 config().group().在构建bootstrap时有传入2个group(boosgroup,workgroup),这里是从AbstractBootstrapConfig里获取的group,是bossgroup(调试结果)。

继续跟踪代码,最终会到SingleThreadEventLoop的register()

netty源码分析_第25张图片

 继续往下到AbstractChannel#register0,最终会调用到AbstractNioChannel#doRegistrr()

netty源码分析_第26张图片

 到这里,已经完成了NioServerSocketChannel实例和Selector的注册

到这里,咱们可能还存在2个疑问,1 端口号绑定是在哪里处理? 2 NioEventLoop的循环拉取事件的方法还没有触发?

端口号绑定

netty源码分析_第27张图片

跟踪代码,到AbstractBootstrap#doBind0

netty源码分析_第28张图片

netty源码分析_第29张图片

 调试最终会调用到NioServerSocketChannel#doBind,最终获取java nio中提供的ServerSocketChannel做bind操作

触发NioEventLoop的循环操作拉取事件

在上面的组件介绍时,有给过NioEventLoop的类继承图,再次给出

netty源码分析_第30张图片

 所以EventLoop肯定也有执行线程任务的功能:

netty源码分析_第31张图片

 通过搜索方法,发现在NioEventLoop的父类SingleThreadEventExecutor中有对execute方法的重写,时序图如下:

SingleThreadEventExecutor#execute(Runnable)

execute(Runnable,boolean)
startThread()
doStartThread()
SingleThreadEventExecutor.this.run();

NioEventLoop#run();

(以上方法调用链的寻找,是通过逆向寻找的)

所以现在的关键是:什么时候触发了SingleThreadEventExecutor的execute方法?

我们在做initAndRegister方法分析时,有关于该方法触发的入口:AbstractChannel#register

netty源码分析_第32张图片

 至此,netty的核心组件,启动过程的大致过程已经分析完毕,其中忽略掉了很多调用细节,包括:EventLoop中2个队列,Unsafe类的分析等等,因为和主流程(对比nio)无关,所以这里选择性的忽略,有兴趣的同学可以自己调试分析

网络连接事件入口及事件驱动流程

在nio中,网络连接事件入口是:selector.select(),在分析EventLoop的源码时有提到过,在循环拉去网络事件方法中也有相同的代码:

netty源码分析_第33张图片

netty源码分析_第34张图片

 断点调试:

netty源码分析_第35张图片

 netty源码分析_第36张图片

 这里就是通过连接事件读取连接数据

一路调试,会在AbstractNioMessageChannel#read方法中完成消息的读取

netty源码分析_第37张图片

 其中:readBuf是一个SocketChannel数组

netty源码分析_第38张图片

 到这里,就是pipeLine链式触发handler的经典过程了

大致过程如下:

Pipeline中包含一个有ChannelHandlerContext组成的双向链表,在往pipeLine中添加handler时,会将handler构建成一个ChannelHandlerContext,加入到双向链表,当pipeline触发fireChannelRead时

PipeLine#fireChannelRead

AbstractChannelHandlerContext.invokeChannelRead(head, msg);

Pipeline#HeadContext#invokeChannelRead(msg);

Pipeline#HeadContext#channelRead(msg)

AbstractChannelHandlerContext.fireChannelRead( msg);

AbstractChannelHandlerContext.invokeChannelRead(findContextInbound(mask), msg);

然后一直往后迭代pipe链的链表,往后执行,最终会调用我们自定义的handler完成业务处理

以上关于netty的源码分析已经完成,其中有部分内容做了忽略处理,感兴趣的同学可以自行调试,因为涉及到多个异步调用逻辑:bossgroup只负责连接事件,workerGroup负责读写事件等等的区分,还有EventLoop里面的2个队列的处理逻辑等等都没有涉及

借用一张netty架构图:

 详细的阐述了netty运行机制工作图

22.9.14---补充 关于boosgroup和workerGroup的分工是在哪里实现的

Bossgroup知处理链接事件,workgroup处理其他的读写事件

在前面的章节中已经粗略的对netty的整个运行机制有了大概的图文讲解,相信跟着源码调试的同学已经有所体会,在上面的内容中,有选择行的遗漏掉了关于Bossgroup和WorkerGroup分工的实现机理,这里补充上

总结一下之前的内容:

1   每个(NioServerSocketChannel)和SocketChannel都有一个PipeLine对象,PipeLine对象也持有该channel对象

2  Pipeline中有一个由ChannelHandlerContext构建的双向链表

3  往Pipeline中添加handler时,先构建成ChannelHandlerContext,再添加到双向链表的

4  NioEventLoop中有Selector,Executor(执行器),任务队列,有个循环执行方法(通过向执行器中添加任务触发循环方法执行)

5  Pipeline调用fire方法时,通过调用AbstractChannelHandlerContext的入口方法,依次触发双向链表中的每个handler的方法执行

以上就是对前面内容的概括性总结!

Bossgroup和WorkerGroup分工在哪里体现的?

在ServerBootstrap的bind方法调用时,有意的回避了该问题,现截图说明如下:

netty源码分析_第39张图片

 netty源码分析_第40张图片

 从图上可以看到,在做init初始化操作时,有往Bossgroup的Channel的Pipeline中添加ServerBootstrapAcceptor

查看该类的ChannelRead方法(因为pipeline再触发调用链路时会触发所有的handler执行)

netty源码分析_第41张图片

从这里可以看出workerGroup再这里注册上了事件监听,完成了链接事件和工作事件分不同线程处理的机制

 同时,在AbstractChannel的register0方法有关于ChannelInitializer#initChannel的调用入口,此时就把整个逻辑串起来了

netty源码分析_第42张图片

另外:NioServerSocketChannel和NioSocketChannel的类继承图稍有不同:

NioServerSocketChannel:

netty源码分析_第43张图片

而NioSocketChannel:

netty源码分析_第44张图片

NioMessageUnsafe和NioSocketChannelUnsafe两个类的read处理逻辑不一样。一个读取到的是NioSocketChannel一个读取到的是传递的信息 

遗留问题二:一直说bossgroup是处理连接事件,workerGroup处理读事件,在哪里体现的

在以上的分析过程中,细心的同学可能已经发现了一个大问题?

NioServerSocketChannel在调用jdk的ServerSocketChannel注册时,传入的ops是0.而我们正常使用nio时传入的ops是16也就是SelectionKey.OP_ACCEPT。我们都知道当通道做注册时,如果注册的是0时,在selector.select时是获取不到相关事件的

netty源码分析_第45张图片

那netty又是怎么拿到连接事件的呢?或者还有其他的手段可以拿到?

在nio中,提供了如下代码可以修改注册感兴趣事件:

 netty源码分析_第46张图片

所以netty应该也是在后续的代码中有对channel感兴趣事件的修改。跟踪代码,发现在如下代码中有如上骚操作:

netty源码分析_第47张图片 什么时候触发的?

netty源码分析_第48张图片

 当channel信道注册上Selector之后,会触发pipeLine的激活事件,该事件最终会调用到

AbstractNioChannel#doBeginRead()方法,此时会触发修改通道感兴趣事件

AbstractNioChannel中的字段readInterestOp是什么时候赋值的?

在构造时候赋值的

netty源码分析_第49张图片

 同时,查看NioServerSocketChannel和NioSocketChannel可以看到

netty源码分析_第50张图片

 

netty源码分析_第51张图片 

 至此,netty的源码分析工作已经全部完成,在主流程上,没有留遗漏问题点。至于其他的一些细节问题:包括任务队列的运转等可以自行调试完成

创作不易,如果觉得有用,请帮忙点赞收藏

 
  

你可能感兴趣的:(nio,java,spring)