Netty源码分析-ChannelHandler方法执行顺序和如何工作

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

Netty版本:Netty版本:netty-4.1.75.Final

1. 前言

在之前的文章《Netty组件-ChannelHandler(图文并茂) 》中了解了ChannelHandler同时对其两个继承接口ChannelInboundHandler和ChannelOutboundHandler都有了一定的了解,从如下几个方面来对ChannelHandler通过源码进一步解析:

image.png

Tips: 上图后面两个内容会在后续的文章更新

2. ChannelHandler方法执行顺序

ChannelHandler方法执行顺序执行顺序其实说的是三个类:ChannelHandler、ChannelInboundHandler、ChannelOutboundHandler 这三个类的方法执行顺序。通过一个简答的Netty例子来打印一下执行的顺序。代码比较多这里就不直接粘贴出来了,已经上传到github仓库,可以下载到本地运行。

image.png

代码github地址:github.com/mxsm/spring…

运行结果分为两个部分:

  • 服务端结果

image.png

  • 客户端结果

image.png

接下来对运行结果结合源码进行分析。客户端和服务器端ChannelHandler的执行大部分相同,只有细小出的区别。我们会在有区别的地方进行说明

Tips: 服务端线程模型是主从模型,所以我们会分别分析Boss线程中的ChannelHandler以及Work线程中的ChannelHandler。

大致流程:

image.png

下面分析如果没有特别说明都是以服务端为例进行源码分析。

2.1 ChannelHandler#handlerAdded

image.png

Tips:代码地址github.com/mxsm/spring…

主要关注上图红框部分的代码,通过跟进代码会发现 ServerBootstrap 创建后会创建一个 NioServerSocketChannel 实例,然后调用 ServerBootstrap#init 方法进行初始化,如下图所示:

image.png

如上图代码所示,我这边把这里圈成了三个部分:

Tips: ChannelInitializer其实就是一个ChannelInboundHandlerAdapter

  1. NioServerSocketChannel的实例的ChannelPipeline中添加ChannelInitializer,那么ChannelInitializer#initChannel 什么时候触发,如下代码:

image.png

在触发 channelRegistered 方法后调用了 ChannelInitializer#initChannel 这个私有方法,私有方法又调用了 ChannelInitializer#initChannel 抽象方法。

  1. NioServerSocketChannel的实例的ChannelPipeline添加ServerBootstrap#handler方法设置的ChannelHandler。对应上面的例子就是这段代码里面的ChannelHandler,如下图标号1位置所示:

image.png

  1. 将数据处理交给Worker线程,也是通过这个地方进行的。(后续会专门写一篇文章来说主负线程如何配合工作)

总结:从上面的分析可以看出来ChannelHandler#handlerAdded方法的触发,主要是通过ChannelPipeline的add类型方法来触发,底层是通过AbstractChannelHandlerContext#callHandlerAdded调用来实现。

2.2 ChannelInboundHandler#channelRegistered

在调用ServerBootstrap#bind方法当中,ServerSocketChannel初始化后,将ServerSocketChannel注册到BossGroup上,如下图所示:

image.png 上图标号1所示位置就是将ServerSocketChannel注册到BossGroup。跟进代码最终调用的是AbstractChannel#register0 方法,如下图所示:

image.png

如上图标号2位置就是触发ChannelInboundHandler#channelRegistered方法。

总结:ChannelInboundHandler#channelRegistered方法触发是在往EventLoopGroup中添加Channel的时候触发

Tips: 上面说的都是触发NioServerSocketChannel实例中的ChannelHandler,也就是BossGroup中。workGroup中的ChannelHandler触发在哪里触发呢?之前ChannelHandler#handlerAdded章节分析图中有个标号3的位置中的代码就是触发childHandler的channelRegistered方法的:

image.png

2.3 ChannelOutboundHandler#bind

当NioServerSocketChannel创建、初始化、注册到EventLoopGroup完成后,接下来就进行绑定,与本地端口进行绑定以便接收数据,绑定的工作通过代码分析发现最后调用的是 AbstractBootstrap#doBind0 方法,如下图所示:

image.png

Tips: 这个地方的channel变量其实就是NioServerSocketChannel的实例。

通过跟进bind方法的代码可以发现最终调用的是 AbstractChannelHandlerContext#invokeBind

image.png

**总结:ChannelOutboundHandler#bind调用是服务端的Channel绑定本地地址触发,如NioServerSocketChannel绑定本地地址端口准备接受客户端数据 **

Tips: ChannelOutboundHandler#bind是BossGroup的Channel所特有,在childHandler中不会执行。

2.4 ChannelInboundHandler#channelActive

ChannelInboundHandler#channelActive的触发需要分两种情况:

  1. BossGroup中的ServerSocketChannel触发
  2. WorkerGroup中的SocketChannel触发

首先看一下BossGroup中的ServerSocketChannel触发中的触发,在执行NioServerSocketChannel#bind ,触发了自定义的TimeServerBossOutHandler#bind

image.png

上图标号1又调用了父类的bind方法,最终调用了AbstractChannel.AbstractUnsafe#bind

image.png

上图1位置就是触发ChannelInboundHandler#channelActive。

Tips: TimeServerBossOutHandler#bind代码中去掉 super.bind(ctx, localAddress, promise); 这段代码,你用客户端链接发现连不上。这就是因为NioServerSocketChannel没有绑定。

WorkerGroup中的SocketChannel触发如何触发?服务端接收到连接请求处理由BossGroup处理,读写操作是由WorkGroup处理,那么这个转换就是在ServerBootstrap#init 方法中完成,如下图代码所示:

image.png

上图框出来的代码 ServerBootstrapAcceptor 其实也是一个ChannelInboundHandler

image.png

上图框出来的就是往WorkGroup中注册Channel。所有这里会触发 ChannelInboundHandler#channelRegistered

Tips: BossGroup注册NioServerSocketChannel和WorkGroup注册SocketChannel两者触发ChannelInboundHandler#channelRegistered 的逻辑没有区别。

注册最终也是调用了AbstractChannel.AbstractUnsafe#register0 :

image.png

BossGroup注册NioServerSocketChannel和WorkGroup注册NioSocketChannel区别在于上图标号1的isActive() 方法,这个方法是一个抽象方法。根据不同的类似实现。

  • NioServerSocketChannel实现isActive()

image.png

  • NioSocketChannel实现isActive()

image.png

所以会进入if条件语句中,加上又是第一次注册,最终会触发标号为2的方法。

总结:NioServerSocketChannel和NioSocketChannel触发ChannelInboundHandler#channelActive不一样,但是都是当Channel可用的时候触发

2.5 ChannelOutboundHandler#read

AbstractChannelHandlerContext#invokeChannelActive方法主要触发channelActive如下图:

image.png

然后通过调用AbstractChannelHandlerContext#invokeChannelActive方法:

image.png

通过上图可以知道最终调用的是DefaultChannelPipeline.HeadContext#channelActive方法

image.png

然后调用DefaultChannelPipeline.HeadContext#readIfIsAutoRead

image.png

然后调用AbstractChannel#read方法,这个方法中调用了ChannelPipeline#read 方法触发ChannelOutboundHandler#read。

总结:ChannelOutboundHandler#read的触发都是在ChannelInboundHandler#channelActive,通过DefaultChannelPipeline.HeadContext#readIfIsAutoRead方法实现。

2.6 ChannelInboundHandler#channelRead

ServerSocketChannel还是SocketChannel都是通过NioEventLoop#processSelectedKey方法中一下代码触发unsafe.read():

image.png

这里根据ServerSocketChannel还是SocketChannel执行不同的Unsafe实现。

ServerSocketChannel也就是BossGroup执行的是AbstractNioMessageChannel.NioMessageUnsafe#read 方法:

image.png

标号1触发ChannelInboundHandler#channelRead,标号2触发ChannelInboundHandler#channelReadComplete。

SocketChannel也就是workGroup执行的是AbstractNioByteChannel.NioByteUnsafe#read方法:

image.png

上图的标号1,2分别触发ChannelInboundHandler#channelRead和ChannelInboundHandler#channelReadComplete。

在workGroup中还有这样两个:

TimeServerOutHandler--write
TimeServerOutHandler--flush
复制代码

那只是为什么? 这个是因为在TimeServerInHandler中调用了如下方法:

image.png

下面就下来分析

2.7 ChannelOutboundHandler#write和ChannelOutboundHandler#flush

通过上面知道要想触发ChannelOutboundHandler#write和ChannelOutboundHandler#flush需要调用ChannelHandlerContext#writeAndFlush,通过代码研究发现最终调用的是AbstractChannelHandlerContext#write:

image.png 标号1查找到ChannelOutboundHandler,然后执行2,执行ChannelOutboundHandler#write或者ChannelOutboundHandler#write和ChannelOutboundHandler#flush。

2.8 ChannelOutboundHandler#channelInactive和ChannelOutboundHandler#channelUnregistered

当客户端关闭服务端调用到如下的代码AbstractNioByteChannel#read方法:

image.png

最终会调用标号2的位置,然后调用AbstractNioByteChannel.NioByteUnsafe#closeOnRead方法:

image.png 跟进代码发现最终调用了AbstractChannel.AbstractUnsafe#deregister方法。在这个方法中有调用如下代码:

image.png

这里就触发了ChannelOutboundHandler#channelInactive和ChannelOutboundHandler#channelUnregistered。

2.8 ChannelHandler#handlerRemoved

上图标号2调用了 pipeline.fireChannelUnregistered(); 方法,最终是调用了DefaultChannelPipeline.HeadContext#channelUnregistered 方法:

image.png

上图标号1的位置就是触发ChannelHandler#handlerRemoved。将当前Channel的ChannelHandler移除从EventLoop上面。

2.9 ChannelOutboundHandler#connect和ChannelOutboundHandler#close

这两个都发生在客户端,整体的触发机制和上面说的大体相同,大家可以自己去进行分析。

3. 总结

Netty的ChannelHandler的整体触发流程如上面所述。其中没有涉及到错误捕捉的触发。

  • ChannelPipeline的双向链表中的HeadContext和TailContext都是ChannelHandler,同时继承了AbstractChannelHandlerContext,也可以说是ChannelHandlerContext。
  • ChannelHander添加到队列中,会被包装成ChannelHandlerContext

Tips: 我是蚂蚁背大象,文章对你有帮助点赞关注我,文章有不正确的地方请您斧正留言评论~谢谢

猜你喜欢

转载自juejin.im/post/7076731940706975758