Netty核心组件解析
- Bootstrap & ServerBootstrap
- EventLoop & EventLoopGroup
- Channel
- ChannelHandler & ChannelPipeline & ChannelHandlerContext
- ChannelHandler
- ChannelPipeline
- ChannelHandlerContext
- ChannelFuture
Bootstrap & ServerBootstrap
Bootstrap和ServerBootstrap是Netty应用程序的启动引导器,通过它可以配置我们的Netty应用程序并启动。其中Bootstrap是Netty客户端使用的启动引导器,而ServerBootstrap则是Netty服务端使用的启动引导器。
EventLoop & EventLoopGroup
EventLoop是事件循环,也是就我们在NIO程序中while循环调用Selector的select()方法进行监听然后处理就绪事件的逻辑,现在Netty通过EventLoop封装事件循环,使得我们无需重复编写事件循环的代码,只需要专注于就绪事件的处理逻辑。
每个EventLoop都对应一个线程,每个EventLoop又属于某个EventLoopGroup,因此EventLoopGroup相当于是线程组。我们常用的是NioEventLoopGroup,NioEventLoopGroup内部包含了一个或多个NioEventLoop,由我们的参数进行设置。
一个NioEventLoop内部包含一个Selector和一个Queue<Runnable>类型的taskQueue。
Selector就是NIO中的Selector,Selector是一个多路复用器,我们可以往Selector注册多个Channel,Selector可以帮我们监听注册在其上的Channel,当我们调用Selector的select()方法时,当前线程就阻塞,通过Selector监听注册在其上的Channel,等待关注的事件就绪。
于是,NioEventLoop内部线程的事件循环,就通过Selector的select()方法监听注册到Selector上的Channel,等待一个或多个Channel有关注的事件就绪。
当有事件就绪时,NioEventLoop内部线程会调用processSelectedKeys()方法处理就绪事件对应的Channel。
当processSelectedKeys()方法调用完毕后,NioEventLoop还会调用runAllTasks()方法处理被提交到自己的taskQueue中的异步任务。一些比较耗时但是实时性不高的任务,我们可以把它提交到NioEventLoop的taskQueue中让它异步处理。
当taskQueue中的任务处理完,一个事件循环就结束,进入下一次循环。
Channel
这里的Channel和NIO的Channel不是同一个东西,这里的Channel是Netty对NIO的Channel经过封装后的属于Netty自己的Channel。比如NIO中的ServerSocketChannel和SocketChannel,Netty把它们封装成了NioServerSocketChannel和NioSocketChannel。
除了持有NIO的Channel以外,还保存了各自关注的事件类型,等真正把NIO的Channel注册到Selector上的时候,就可以直接设置对应的事件类型。NioServerSocketChannel保存的是连接就绪事件OP_ACCEPT,而NioSocketChannel保存的是读就绪事件OP_READ。
在使用Java原生的NIO时,我们都会设置Channel为非阻塞的,也就是调用configureBlocking(false)方法,这里Netty自动帮我们设置为非阻塞了,无需我们手动设置。比如NioServerSocketChannel在构造方法中就调用了ServerSocketChannel的configureBlocking(false)方法。
除了NioServerSocketChannel和NioSocketChannel以外,Netty还有其他类型的Channel,比如BIO类型的Channel、UDP协议的Channel,这里就不列举了。
然后Netty中的每个Channel,都有对应ChannelPipeline用来处理Channel中就绪的事件。
ChannelHandler & ChannelPipeline & ChannelHandlerContext
与Chandler相关的其他组件包括ChannelHandler、ChannelPipeline、ChannelHandlerContext。每个Chandler都会有一个ChandlerPipeline与之对应,用于处理该Chandler上发生的事件,而ChandlerPipeline又会通过它内部的ChandlerHandler去处理到来的事件。
ChannelHandler
ChannelHandler是专门用于处理就绪事件的,我们在开发Netty应用程序时,主要就是编写各种ChannelHandler,在ChannelHandler中各种事件触发的方法中实现自己的处理逻辑。
ChannelHandler又分为是处理入站事件还是出站事件的。所谓入站事件,就是由于接收到外部的数据或消息等而触发的事件就是入站事件,比如读就绪事件就是入站事件,因为有数据到达才会触发读就绪事件;而出站事件则与入站事件相反,由自己主动触发的,流动方向是向外的事件就是出站事件,比如写就绪事件。
如果要编写处理入站事件的ChannelHandler,我们可以实现ChannelInboundHandler接口,ChannelInboundHandler就是专门用于处理入站事件的;如果要编写处理出站事件的ChannelHandler,我们可以实现ChannelOutboundHandler接口,ChannelOutboundHandler就是专门用于处理出站事件的。
ChannelInboundHandler和ChannelOutboundHandler都继承了ChannelHandler接口。
但是ChannelInboundHandler和ChannelOutboundHandler有许多不同类型事件对应的方法,如果直接实现ChannelInboundHandler或ChannelOutboundHandler,我们要一一编写每个方法的实现逻辑。但是我们可能不会全部事件都关注,可能只会关注一两种类型的事件。
于是我们可以通过继承Netty提供的适配器类来实现我们的ChannelHandler,我们就可以选择性的实现我们关注的事件类型对应的方法。Netty提供的适配器类就是ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter,ChannelInboundHandlerAdapter是用于处理入站事件的适配器类,ChannelOutboundHandlerAdapter是用于处理出站事件的适配器类。
ChannelInboundHandlerAdapter实现了ChannelInboundHandler接口,而ChannelOutboundHandlerAdapter则实现了ChannelOutboundHandler接口。
ChannelPipeline
在Netty中,每个Channel对应一个ChannelPipeline。在Channel初始化的时候,就会为Channel创建一个对应的ChannelPipeline。ChannelPipeline是一个由多个ChannelHandler组成的双向链表,ChannelPipeline中有固定的head(头部Handler)和tail(尾部Handler)。
ChannelPipeline中除head和tail以外的ChannelHandler可以通过ChannelInitializer进行安装,一开始ChannelPipeline中除head和tail以外,中间只有一个ChannelInitializer。当Channel(NIO的Channel)被注册到Selector中时,会触发ChannelInitializer的调用,安装指定的ChannelHandler到Pipeline中,并把自己从链表中删除。
ChannelPipeline中对应就绪事件的处理,就是调用ChannelPipeline中的ChannelHandler与就绪事件匹配的方法。如果是入栈事件,则会以从head到tail的方向逐一调用负责处理入站事件的ChannelHandler来处理;如果是出站事件,则会以tail到head的方向逐一调用负责处理出站事件的ChannelHandler来处理。
ChannelHandlerContext
其实ChannelPipeline中的双向链表并不是由ChannelHandler直接组成的双向链表,而是在ChannelHandler外头再包了一层ChannelHandlerContext,因为ChannelHandler本身并没有维护组成双向链表需要的前后指针,ChannelHandlerContext才维护了这个前后指针,也就是说ChannelPipeline里头是由ChannelHandlerContext组成的双向链表。
之所以要在ChannelHandler之上再包一层ChannelHandlerContext来组成链表,是出于单一职责的考虑。首先ChannelHandler是用来处理事件的,那么ChannelHandler就应该只关注事件的处理,让它去维护前后指针就不太合适。就像LinkedList一样,我们放进去的元素,也不是直接组成双向链表的,LinkedList里头也是会包一个Node,再由Node的前后指针组成双向链表。
以下是ChannelHandlerContext的一些常用方法:
其中fireXxx是ChannelHandlerContext定义的以fire开头的一些方法,这些方法都是用于触发ChannelHandler中不同类型的事件处理方法,一旦调用这些方法,就会从当前ChannelHandlerContext开始沿着责任链调用每个ChannelHandler对应的事件的处理方法。比如调用了当前ChannelHandlerContext的fireChannelRead(Object msg)方法,就会从当前ChandlerHandlerContext对应的ChandlerHandler开始,沿着责任链顺序调用每个ChandlerHandler的channelRead(…)方法。
ChannelFuture
Netty中的操作都是异步操作,比如我们通过Bootstrap的connect(String inetHost, int inetPort)连接服务器、通过ServerBootstrap的bind(int inetPort)监听某个端口等,这些都是异步操作。这些异步操作都会返回一个ChannelFuture,我们可以通过ChannelFuture的addListener(…)方法添加一个ChannelFutureListener,等Netty的异步操作完成后,会触发添加到ChannelFuture的ChannelFutureListener进行相应的处理。