Netty源码分析-ChannelHandler方法执行顺序和如何工作
Netty版本:Netty版本:netty-4.1.75.Final
1. 前言
在之前的文章《Netty组件-ChannelHandler(图文并茂) 》中了解了ChannelHandler同时对其两个继承接口ChannelInboundHandler和ChannelOutboundHandler都有了一定的了解,从如下几个方面来对ChannelHandler通过源码进一步解析:
Tips: 上图后面两个内容会在后续的文章更新
2. ChannelHandler方法执行顺序
ChannelHandler方法执行顺序执行顺序其实说的是三个类:ChannelHandler、ChannelInboundHandler、ChannelOutboundHandler
这三个类的方法执行顺序。通过一个简答的Netty例子来打印一下执行的顺序。代码比较多这里就不直接粘贴出来了,已经上传到github仓库,可以下载到本地运行。
运行结果分为两个部分:
-
服务端结果
-
客户端结果
接下来对运行结果结合源码进行分析。客户端和服务器端ChannelHandler的执行大部分相同,只有细小出的区别。我们会在有区别的地方进行说明
Tips: 服务端线程模型是主从模型,所以我们会分别分析Boss线程中的ChannelHandler以及Work线程中的ChannelHandler。
大致流程:
下面分析如果没有特别说明都是以服务端为例 进行源码分析。
2.1 ChannelHandler#handlerAdded
主要关注上图红框部分的代码,通过跟进代码会发现 ServerBootstrap
创建后会创建一个 NioServerSocketChannel
实例,然后调用 ServerBootstrap#init
方法进行初始化,如下图所示:
如上图代码所示,我这边把这里圈成了三个部分:
Tips: ChannelInitializer其实就是一个ChannelInboundHandlerAdapter
- NioServerSocketChannel的实例的ChannelPipeline中添加ChannelInitializer,那么ChannelInitializer#initChannel 什么时候触发,如下代码:
在触发 channelRegistered
方法后调用了 ChannelInitializer#initChannel 这个私有方法,私有方法又调用了 ChannelInitializer#initChannel 抽象方法。
-
NioServerSocketChannel的实例的ChannelPipeline添加ServerBootstrap#handler方法设置的ChannelHandler。对应上面的例子就是这段代码里面的ChannelHandler,如下图标号1位置所示:
-
将数据处理交给Worker线程,也是通过这个地方进行的。(后续会专门写一篇文章来说主负线程如何配合工作)
总结:从上面的分析可以看出来ChannelHandler#handlerAdded方法的触发,主要是通过ChannelPipeline的add类型方法来触发,底层是通过AbstractChannelHandlerContext#callHandlerAdded调用来实现。
2.2 ChannelInboundHandler#channelRegistered
在调用ServerBootstrap#bind
方法当中,ServerSocketChannel初始化后,将ServerSocketChannel注册到BossGroup上,如下图所示:
上图标号1所示位置就是将ServerSocketChannel注册到BossGroup。跟进代码最终调用的是AbstractChannel#register0
方法,如下图所示:
如上图标号2位置就是触发ChannelInboundHandler#channelRegistered
方法。
总结:ChannelInboundHandler#channelRegistered方法触发是在往EventLoopGroup中添加Channel的时候触发
Tips: 上面说的都是触发NioServerSocketChannel实例中的ChannelHandler,也就是BossGroup中。workGroup中的ChannelHandler触发在哪里触发呢?之前ChannelHandler#handlerAdded章节分析图中有个标号3的位置中的代码就是触发childHandler的channelRegistered方法的:
2.3 ChannelOutboundHandler#bind
当NioServerSocketChannel创建、初始化、注册到EventLoopGroup完成后,接下来就进行绑定,与本地端口进行绑定以便接收数据,绑定的工作通过代码分析发现最后调用的是 AbstractBootstrap#doBind0
方法,如下图所示:
Tips: 这个地方的channel变量其实就是NioServerSocketChannel的实例。
通过跟进bind方法的代码可以发现最终调用的是 AbstractChannelHandlerContext#invokeBind
**总结:ChannelOutboundHandler#bind调用是服务端的Channel绑定本地地址触发,如NioServerSocketChannel绑定本地地址端口准备接受客户端数据 **
Tips: ChannelOutboundHandler#bind是BossGroup的Channel所特有,在childHandler中不会执行。
2.4 ChannelInboundHandler#channelActive
ChannelInboundHandler#channelActive的触发需要分两种情况:
- BossGroup中的ServerSocketChannel触发
- WorkerGroup中的SocketChannel触发
首先看一下BossGroup中的ServerSocketChannel触发中的触发,在执行NioServerSocketChannel#bind
,触发了自定义的TimeServerBossOutHandler#bind
:
上图标号1又调用了父类的bind方法,最终调用了AbstractChannel.AbstractUnsafe#bind
:
上图1位置就是触发ChannelInboundHandler#channelActive。
Tips: TimeServerBossOutHandler#bind代码中去掉
super.bind(ctx, localAddress, promise);
这段代码,你用客户端链接发现连不上。这就是因为NioServerSocketChannel没有绑定。
WorkerGroup中的SocketChannel触发如何触发?服务端接收到连接请求处理由BossGroup处理,读写操作是由WorkGroup处理,那么这个转换就是在ServerBootstrap#init
方法中完成,如下图代码所示:
上图框出来的代码 ServerBootstrapAcceptor
其实也是一个ChannelInboundHandler
:
上图框出来的就是往WorkGroup中注册Channel。所有这里会触发 ChannelInboundHandler#channelRegistered 。
Tips: BossGroup注册NioServerSocketChannel和WorkGroup注册SocketChannel两者触发ChannelInboundHandler#channelRegistered 的逻辑没有区别。
注册最终也是调用了AbstractChannel.AbstractUnsafe#register0
:
BossGroup注册NioServerSocketChannel和WorkGroup注册NioSocketChannel区别在于上图标号1的isActive()
方法,这个方法是一个抽象方法。根据不同的类似实现。
-
NioServerSocketChannel实现isActive()
-
NioSocketChannel实现isActive()
所以会进入if条件语句中,加上又是第一次注册,最终会触发标号为2的方法。
总结:NioServerSocketChannel和NioSocketChannel触发ChannelInboundHandler#channelActive不一样,但是都是当Channel可用的时候触发
2.5 ChannelOutboundHandler#read
AbstractChannelHandlerContext#invokeChannelActive
方法主要触发channelActive如下图:
然后通过调用AbstractChannelHandlerContext#invokeChannelActive方法:
通过上图可以知道最终调用的是DefaultChannelPipeline.HeadContext#channelActive
方法
然后调用DefaultChannelPipeline.HeadContext#readIfIsAutoRead
然后调用AbstractChannel#read
方法,这个方法中调用了ChannelPipeline#read
方法触发ChannelOutboundHandler#read。
总结:ChannelOutboundHandler#read的触发都是在ChannelInboundHandler#channelActive,通过DefaultChannelPipeline.HeadContext#readIfIsAutoRead方法实现。
2.6 ChannelInboundHandler#channelRead
ServerSocketChannel还是SocketChannel都是通过NioEventLoop#processSelectedKey方法中一下代码触发unsafe.read():
这里根据ServerSocketChannel还是SocketChannel执行不同的Unsafe实现。
ServerSocketChannel也就是BossGroup执行的是AbstractNioMessageChannel.NioMessageUnsafe#read
方法:
标号1触发ChannelInboundHandler#channelRead,标号2触发ChannelInboundHandler#channelReadComplete。
SocketChannel也就是workGroup执行的是AbstractNioByteChannel.NioByteUnsafe#read方法:
上图的标号1,2分别触发ChannelInboundHandler#channelRead和ChannelInboundHandler#channelReadComplete。
在workGroup中还有这样两个:
TimeServerOutHandler--write
TimeServerOutHandler--flush
那只是为什么? 这个是因为在TimeServerInHandler中调用了如下方法:
下面就下来分析
2.7 ChannelOutboundHandler#write和ChannelOutboundHandler#flush
通过上面知道要想触发ChannelOutboundHandler#write和ChannelOutboundHandler#flush需要调用ChannelHandlerContext#writeAndFlush,通过代码研究发现最终调用的是AbstractChannelHandlerContext#write:
标号1查找到ChannelOutboundHandler,然后执行2,执行ChannelOutboundHandler#write或者ChannelOutboundHandler#write和ChannelOutboundHandler#flush。
2.8 ChannelOutboundHandler#channelInactive和ChannelOutboundHandler#channelUnregistered
当客户端关闭服务端调用到如下的代码AbstractNioByteChannel#read方法:
最终会调用标号2的位置,然后调用AbstractNioByteChannel.NioByteUnsafe#closeOnRead方法:
跟进代码发现最终调用了AbstractChannel.AbstractUnsafe#deregister方法。在这个方法中有调用如下代码:
这里就触发了ChannelOutboundHandler#channelInactive和ChannelOutboundHandler#channelUnregistered。
2.8 ChannelHandler#handlerRemoved
上图标号2 调用了 pipeline.fireChannelUnregistered();
方法,最终是调用了DefaultChannelPipeline.HeadContext#channelUnregistered
方法:
上图标号1的位置就是触发ChannelHandler#handlerRemoved。将当前Channel的ChannelHandler移除从EventLoop上面。
2.9 ChannelOutboundHandler#connect和ChannelOutboundHandler#close
这两个都发生在客户端,整体的触发机制和上面说的大体相同,大家可以自己去进行分析。
3. 总结
Netty的ChannelHandler的整体触发流程如上面所述。其中没有涉及到错误捕捉的触发。
- ChannelPipeline的双向链表中的HeadContext和TailContext都是ChannelHandler,同时继承了AbstractChannelHandlerContext,也可以说是ChannelHandlerContext。
- ChannelHander添加到队列中,会被包装成ChannelHandlerContext
Tips: 我是蚂蚁背大象,文章对你有帮助点赞关注我,文章有不正确的地方请您斧正留言评论~谢谢