Skip to main content

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

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

1. 前言

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

ChannelHandler解析点

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

2. ChannelHandler方法执行顺序

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

image-20220313142535666

代码github地址:https://github.com/mxsm/spring-sample/tree/master/java-sample/src/main/java/com/github/mxsm/netty/channelhandler

运行结果分为两个部分:

  • 服务端结果

    image-20220313143016219

  • 客户端结果

    image-20220313143110607

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

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

大致流程:

ChannelHandler方法执行分析流程

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

2.1 ChannelHandler#handlerAdded

image-20220313144554334

Tips:代码地址https://github.com/mxsm/spring-sample/blob/master/java-sample/src/main/java/com/github/mxsm/netty/channelhandler/DiscardServer.java

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

image-20220313145719703

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

Tips: ChannelInitializer其实就是一个ChannelInboundHandlerAdapter

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

image-20220313150218471

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

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

    image-20220313150751020

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

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

2.2 ChannelInboundHandler#channelRegistered

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

image-20220313152322696

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

image-20220313152757422

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

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

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

image-20220313153504750

2.3 ChannelOutboundHandler#bind

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

image-20220313154352156

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

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

image-20220313154614406

**总结: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-20220313202937928

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

image-20220313203458047

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

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

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

image-20220313205348929

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

image-20220313205548252

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

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

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

image-20220313210108887

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

  • NioServerSocketChannel实现isActive()

    image-20220313210426171

  • NioSocketChannel实现isActive()

    image-20220313210603668

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

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

2.5 ChannelOutboundHandler#read

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

image-20220313213615164

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

image-20220313214123319

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

image-20220313214354015

然后调用DefaultChannelPipeline.HeadContext#readIfIsAutoRead

image-20220313214604152

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

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

2.6 ChannelInboundHandler#channelRead

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

image-20220319090947767

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

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

image-20220319091352613

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

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

image-20220319093237906

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

在workGroup中还有这样两个:

TimeServerOutHandler--write
TimeServerOutHandler--flush

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

image-20220319093647383

下面就下来分析

2.7 ChannelOutboundHandler#write和ChannelOutboundHandler#flush

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

image-20220319095241942

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

2.8 ChannelOutboundHandler#channelInactive和ChannelOutboundHandler#channelUnregistered

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

image-20220319160330612

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

image-20220319160515607

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

image-20220319161139680

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

2.8 ChannelHandler#handlerRemoved

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

image-20220319162739667

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

2.9 ChannelOutboundHandler#connect和ChannelOutboundHandler#close

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

3. 总结

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

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

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