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

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

 

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值