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<SelectionKey> 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通信通道事件)
这是EventLoop的接口层面描述,我们在使用得最多的是NioEventLoop,首先看一下他的类继承图:
从名字看可知,该组件做的大部分和线程处理相关的事情
构造器:
在NioEventLoop的构造器中,我们看到了熟悉的Selector,说明NioEventLoop是持有了Selector组件的,其次可以发现,NioEventLoop里面有个很长的方法:
里面有涉及到关于nio相关的事件类型(SelectionKey.OP_READ等等)的逻辑 入口:
netty的channel
首先看一下netty的channel通信通道定义
然后我们看一下使用得最多的netty的通信通道的类图
在nio中channel做为通信通道,做了2个动作。1 绑定端口号 2 用作通信通道,完成读,写,在netty的Channel中只是做了继承封装,相信也会有这2个动作的触发
构造器:
继续追踪构造器,最终在netty的顶层通信通道中发现如下逻辑:
可以发现,在NioServerSocketChannel(NioSocketChannel一样)中会由顶层抽象类构建pipeLine
换句话说:netty的channel会持有pipeLine对象
PipeLine:netty的消息推送链
接口定义
netty的pipeLine接口定义如上,可以看到,pipeLine有继承三个接口,包括:ChannelInboundInvoker,ChannelOutboundInvoker,迭代器
其中两个Invoker先简单的理解成ChannleInbound和Channeloutboun的包装类
构造器:
在netty中pipeLine的默认实现:DefaultChannelPipeline
可以看到如下信息:
channel和PipeLine是相互持有
在pipeLine中有一个由ChannelHandlerContext(TailContext,HeadContext均是ChannelHandlerContext的实现内部类)组成的双向链表
#addLast(ChannelHandler)
最终会调用如下代码:
如上,在添加handler时,会首先把handler构建成ChannelHandlerContext,然后添加到pipeLine的双向链路尾部(这个是后续触发读写的数据基础)
ChannelHandlerContext
接口定义
由接口的定义说明可知:一个Context肯定有一个handler与之对应,换言之:ChannelHandlerContext里面会包含一个Handler
构造器:
在netty中ChannelHandlerContext的默认实现是DefaultChannelHandlerContext
由构造器可知的消息:Context持有一个Handler,且持有一个PipeLine对象(在介绍Pipeline时,context会被加入到双向链表中)
重要方法
DefaultChannelHandlerContext的父类:AbstractChannelContext中包含了很多关于fire方法,这些是和netty整个事件驱动模型有关的方法。举其中一个关于读的方法举例:
#invokeChannelRead(Context,msg);
#invokeChannelRead(msg);
ChannelHandler
在博客开头的关于那段注释:处理具体的消息。如果自己抽象出来是不是也是和ChannelHandler的功能差不多?相信netty的开发者的初衷也是如此
在netty中,有2类handler,InboundHandler和OutboundHandler,
Inbound是处理关于读事件的,Outbound是处理关于写事件的
相信通过以上关于netty各组件的分析,对各组件功能已经有了个大概了解,下面分析一下netty整个启动过程
api定式
EventLoopGroup vs EventLoop
在前面的组件介绍中,有介绍过EventLoop,而EventLoopGroup可以简单粗暴的理解成就是一组EventLoop,不信?咱们看看EventLoopGroup源码
EventLoopGroup接口定义:
NioEventLoopGroup的类图如下:
构造器:
追中NioEventLoopGroup的构造器,在MutithreadEventExecutorGroup中会构造一个EventExecutor数组(EventLoop是EventExecutor的子接口)
EventExecutor数据在初始化时,最终会构造:NioEventLoop
在上面组件介绍时,已经说过了,NioEventLoop里面有包含Selector和循环选择处理逻辑,现在关注的问题是:何时触发了循环select逻辑,即:在NioEventLoop中什么时候触发了方法:
灵魂入口 Bootstrap.bind()
既然bootstrap.bind是核心方法,那这个方法到底做了什么处理逻辑?
看来,核心逻辑在initAndRegister(),继续跟踪
config().group().在构建bootstrap时有传入2个group(boosgroup,workgroup),这里是从AbstractBootstrapConfig里获取的group,是bossgroup(调试结果)。
继续跟踪代码,最终会到SingleThreadEventLoop的register()
继续往下到AbstractChannel#register0,最终会调用到AbstractNioChannel#doRegistrr()
到这里,已经完成了NioServerSocketChannel实例和Selector的注册
到这里,咱们可能还存在2个疑问,1 端口号绑定是在哪里处理? 2 NioEventLoop的循环拉取事件的方法还没有触发?
端口号绑定
跟踪代码,到AbstractBootstrap#doBind0
调试最终会调用到NioServerSocketChannel#doBind,最终获取java nio中提供的ServerSocketChannel做bind操作
触发NioEventLoop的循环操作拉取事件
在上面的组件介绍时,有给过NioEventLoop的类继承图,再次给出
所以EventLoop肯定也有执行线程任务的功能:
通过搜索方法,发现在NioEventLoop的父类SingleThreadEventExecutor中有对execute方法的重写,时序图如下:
SingleThreadEventExecutor#execute(Runnable)
execute(Runnable,boolean)
startThread()
doStartThread()
SingleThreadEventExecutor.this.run();
NioEventLoop#run();
(以上方法调用链的寻找,是通过逆向寻找的)
所以现在的关键是:什么时候触发了SingleThreadEventExecutor的execute方法?
我们在做initAndRegister方法分析时,有关于该方法触发的入口:AbstractChannel#register
至此,netty的核心组件,启动过程的大致过程已经分析完毕,其中忽略掉了很多调用细节,包括:EventLoop中2个队列,Unsafe类的分析等等,因为和主流程(对比nio)无关,所以这里选择性的忽略,有兴趣的同学可以自己调试分析
网络连接事件入口及事件驱动流程
在nio中,网络连接事件入口是:selector.select(),在分析EventLoop的源码时有提到过,在循环拉去网络事件方法中也有相同的代码:
断点调试:
这里就是通过连接事件读取连接数据
一路调试,会在AbstractNioMessageChannel#read方法中完成消息的读取
其中:readBuf是一个SocketChannel数组
到这里,就是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方法调用时,有意的回避了该问题,现截图说明如下:
从图上可以看到,在做init初始化操作时,有往Bossgroup的Channel的Pipeline中添加ServerBootstrapAcceptor
查看该类的ChannelRead方法(因为pipeline再触发调用链路时会触发所有的handler执行)
从这里可以看出workerGroup再这里注册上了事件监听,完成了链接事件和工作事件分不同线程处理的机制
同时,在AbstractChannel的register0方法有关于ChannelInitializer#initChannel的调用入口,此时就把整个逻辑串起来了
另外:NioServerSocketChannel和NioSocketChannel的类继承图稍有不同:
NioServerSocketChannel:
而NioSocketChannel:
NioMessageUnsafe和NioSocketChannelUnsafe两个类的read处理逻辑不一样。一个读取到的是NioSocketChannel一个读取到的是传递的信息
遗留问题二:一直说bossgroup是处理连接事件,workerGroup处理读事件,在哪里体现的
在以上的分析过程中,细心的同学可能已经发现了一个大问题?
NioServerSocketChannel在调用jdk的ServerSocketChannel注册时,传入的ops是0.而我们正常使用nio时传入的ops是16也就是SelectionKey.OP_ACCEPT。我们都知道当通道做注册时,如果注册的是0时,在selector.select时是获取不到相关事件的
那netty又是怎么拿到连接事件的呢?或者还有其他的手段可以拿到?
在nio中,提供了如下代码可以修改注册感兴趣事件:
所以netty应该也是在后续的代码中有对channel感兴趣事件的修改。跟踪代码,发现在如下代码中有如上骚操作:
什么时候触发的?
当channel信道注册上Selector之后,会触发pipeLine的激活事件,该事件最终会调用到
AbstractNioChannel#doBeginRead()方法,此时会触发修改通道感兴趣事件
AbstractNioChannel中的字段readInterestOp是什么时候赋值的?
在构造时候赋值的
同时,查看NioServerSocketChannel和NioSocketChannel可以看到
至此,netty的源码分析工作已经全部完成,在主流程上,没有留遗漏问题点。至于其他的一些细节问题:包括任务队列的运转等可以自行调试完成
创作不易,如果觉得有用,请帮忙点赞收藏