前置知识
学习之前需要理清这么几个关键概念:
- netty相关:EventLoop, EventLoopGroup, ChannelHandler, ChannelPipeline,ChannnelPromise, ChannelFuture
- nio相关:channel, selector.
Channel
与Selector
谈到nio, 那么channel和selector就是绕不开的话题,他们的关系如下:
一个selector和一个线程对应,这也是nio的好处之一:用单线程处理多个客户端的连接;一个selector同时对应多个channel,每个channel将自己注册到selector上,注册的时候会携带自己感兴趣的事件。然后selector就去轮训这些事件,当有某个事件ready时,就去通知对应的channel.
EventLoop
翻译过来是事件循环器,其实就是用来处理各种io事件的,要具备这种能力,那么eventloop必须:
- 具有一个selector
- 具有自己的线程,在这个线程里可以对selector注册的事件进行遍历
所以,eventloop继承自了Executor,具有线程池的能力,同时含有一个selector.

每个channel
都对应一个eventloop
,在netty
启动之时,会将channel
注册到eventloop
,然后eventloop
就会开启死循环,去
- 遍历
channel
感兴趣的事件 - 作为
executor
去执行任务队列里的任务
上述两点便是eventloop
的核心作用。我们已AbstractChannel.register
来说明这个问题。
AbstractChannel.register
1 | public final void register(EventLoop eventLoop, final ChannelPromise promise) { |
在注册channel
的时候,如果当前线程是eventloop
线程,则直接注册,否则,将register0
作为一个任务提交到eventloop
的任务队列里,也就是execute
方法。
SingleThreadEventExecutor.execute

execute
方法也是言简意赅,如果当前线程是eventloop
线程,则直接addTask
,否则先开启一个线程,然后addTask
.
SingleThreadEventExecutor.
doStartThread
1 | private void doStartThread() { |
这里利用Jdk的Executor.execute
开启一个线程,并在这个线程里开始了它的死循环,即SingleThreadEventExecutor.this.run()
NioEventloop.run
1 | protected void run() { |
这个run
方法就是EventLoop
的核心了,里面有两个比较关键的方法:
processSelectedKeys
:查询已经就绪的事件并处理,说白了就是处理io事件runAllTasks
:处理任务队列里的任务,比如我们通过eventloop.execute
提交的任务

那么至此,eventloop
的功能就大概清楚了,它就是在死循环里套死循环,去处理io事件和taskQueue
. 当然了,里面还有一些小细节,比如:
- 为了分配好处理io事件的时间和处理
taskQueue
的事件,使用了ioratio
作为两者的时间比例。
ChannelPipeline
用张图来解释:
每个channel都会对应一个channel pipeline, 而这个pipeline就是一个链表,每个节点类型为ChannelHandlerContext
,内部包了一个ChannelHandler
. 而ChannelHandler
又分为ChannelInboundHandler
和ChannelOutboundHandler
, 分别用来处理入站事件和出站事件,差不多像这样:

当有入站事件或者出站事件发生时,事件会以责任链模式经过handler
.
ChannelPromise
和 ChannelFuture
netty扩展了jdk原生的future. 而promise则是对Netty future的进一步扩展。

jdk原生future:
netty的future:

可以看到,netty扩展的future增加了一些监听器的add
和remove
的方法,以及一些同步方法,如await
,sysnc
.
再来看下promise:
多了一些设置状态的方法,如setSuccess
,setFailure
这些扩展意味着什么
netty的future
可以addListener
,removeLisener
, promise
可以setSuccess
,setFailure
,意味着异步操作时,如主线程调了eventloop的线程,只要将promise
返回主线程,那么promise
在eventloop线程里的任何动作都可以被主线程感知到,比jdk的futrue
更加健壮了一些。
总览
一般的启动代码长这样:
真正的入口在bind
这里,进去看看:

首先,initAndRegister
,意思是初始化并注册,初始化什么?注册什么?这里其实是初始化一个channel, 并把eventloop的selector注册到channel上,注意这个方法的返回值,是个future
,也就是说initAndRegister
这个动作是异步进行的,如果注册完成了,即regFuture.isDone()
,则进行doBind0
操作,否则,添加一个监听器,等initAndRegister
完成了会出发它,然后进行doBind0
。
OK,大致来看,启动分为两个步骤,首先initAndRegister
,然后进行doBind0
。
initAndRegister
首先来看下这个initAndRegister
究竟做了些啥。

这里init
后面会讲。最终调用到的是**AbstractChannel$AbstractUnsage.register
**
AbstractChannel$AbstractUnsage.register

这个register
方法有两个参数,eventloop
是最初配置的bossGroup
,ChannelPromise
是对Channel
的包装。这里要注意,注册的动作必须发生在eventloop
的线程里,所以如果当前线程不是eventloop的线程的话,eventloop
会起一个自己的线程去做这个事情。
继续看register0
:
register0

doResigter
就是真正的注册过程,在死循环里将channel注册到eventloop
的selector
里。
doResigter

相比代码①,注册完成之后的②③④⑤更值得关注。
- 代码②回调添加
handler
时候的handlerAdded
方法
这里是有细节的,回到我们最初的server端代码:
执行完代码②后,应该输出“HandlerAdded”. 但是这个handler是什么时候添加进pipeline
的呢?是serverBootStrap
的handler()
方法吗,不是。回到initAndRegister
处,
abc
看下这个init
方法:
1 |
|
可以看到,在init
的时候,pipeline
就添加一个handler
,即ChannelInitializer
,并覆盖了initChannel
方法,所以启动后pipelie
里除了head
和tail
之外,还有一个叫做ChannelInitializer
的handler
,一共三个handler
。再回到代码②,代码②的最终功能就是去调用ChannelInitializer
的initChannel
方法,而这个initChannle
里面,才是把我们在serverbootstrap
里定义的handler
给加到pipeline
里,并在添加之后回调handlerAdded
方法。

当这个ChannelInitializer
完成的它的任务后,就被remove
掉了,此时pipeline
里就只有head
,tail
以及我们自己定义的handler
了。
- 代码③设置成功之后,我们上文里提到的
doBind
方法里的listener就会被出发,从而继续执行doBind0
方法 - 代码④执行添加handler时候的
channelRegistered
回调 - 代码⑤首次注册的时候不是active,后面再讲
OK,至此,一个channel就初始化好了,并且注册了到了eventloop上
doBind0
书接上回register0
的代码③,设置成功后,doBind
方法里的regFuture
添加的listener
就被触发了,来到了doBind0
环节。

这里的channel.bind()
也是比较有意思的,看似是channel
的bind
,其实最后调的是pipeline
的bind
.(插一嘴,pipieline
在netty里相当核心,基于责任链模式,所有的事件都在pipeline
里流动,后面会专门起一篇文章说这个事情。)
AbstractChannel.bind()

DefaultChannelPipeline.bind()

可以看到,pipeline
的bind
是从tail
开始的,也就是说,它是从尾部向头部传播的,也就是说,context
的类型应该是outboundContenxt
总结一下
服务端启动需要做这么几件事:初始化一个channel, 然后把这个channel注册到selector上,然后把在channel上绑定服务端地址。
整个过程中,pipeline
贯穿全局,起着传递事件的作用。