了解 Netty 中 ServerBootstrap 的启动过程,本篇直接从 io.netty.bootstrap.AbstractBootstrap#doBind
开始,上层一些校验方法就忽略了。
如 示例代码(点击跳转) 中所示,Netty 服务端同时设置了 parentNioEventLoopGroup(主reactor, 以下简称 parentGroup) 和 childNioEventLoopGroup(从reactor, 以下简称 childGroup),即采用了 主从reactor-多线程模型。
网上很多资料都将创建的 NioEventLoopGroup 对象称之为: bossGroup(boss) 和 workGroup(work),这里OP不会这么称呼,而是采用源码中变量的命名,即: parentGroup和 childGroup。
文中图片较大,懒加载,请耐心等候。
本文 Netty 源码解析基于 4.1.86.Final 版本。
1. doBind
doBind 中调用了两个重要方法,启动的核心逻辑就是在 initAndRegister 和 doBind0 两个方法中完成的。
line: 6 -> initAndRegister 方法负责创建、初始化 NioServerSocketChannel ,并且将 NioServerSocketChannel 注册到 parentGroup 中某个 NioEventLoop 的 Selector(多路复用器)[1] 上。
line: 14 或 line: 25 -> doBind0 方法用于端口绑定。
1 | // io.netty.bootstrap.AbstractBootstrap#doBind |
2. initAndRegister
initAndRegister 方法分为三个步骤:
- line:6 -> 通过 ChannelFactory 创建 NioServerSocketChannel,由 serverBootstrap.channel 方法设置。
- line:8 -> 初始化 NioServerSocketChannel。
- line:12 -> 将 NioServerSocketChannel 注册到 parentGroup 中某个 NioEventLoop 的 Selector(多路复用器)[1] 上。
1 | final ChannelFuture initAndRegister() { |
2.1. 创建Channel
channelFactory 属性是由 serverBootstrap.channel
方法设置的,在 Netty_浅看ServerBootstrap配置 的「channel方法」一节中介绍过,这里不再赘述。
结合 示例代码(点击跳转) ,constructor.newInstance()
方法调用的是 NioServerSocketChannel 的无参构造函数,创建了 NioServerSocketChannel 对象。
1 | channel = channelFactory.newChannel(); |
关于 NioServerSocketChannel,概括来说:NioServerSocketChannel 是非阻塞的, 并且关注 OP_ACCEPT 事件, 具体读写细节在内部类 NioMessageUnsafe 中,会依次处理客户端连接最后统一触发 ChannelRead 事件。
具体内容详阅: Netty_NioServerSocketChannel与NioSocketChannel ,此处不再赘述。
2.2. 初始化Channel
初始化 NioServerSocketChannel 分三个部分: 配置TCP参数、配置自定义属性参数、装配 pipeline 流水线。
1 | void init(Channel channel) { |
为 NioServerSocketChannel 装配 pipeline 流水线,需要注意以下信息:
line:25 -> NioServerSocketChannel 初始化时,会向 pipeline 中添加一个 ChannelInitializer 处理器。这是一个特殊的 入站处理器,它的 initChannel 方法在该 NioServerSocketChanne 注册到 NioEventLoop 的 Selector(多路复用器)[1] 后才会被触发调用;
确切的说是在 NioServerSocketChanne 注册完成后,触发 handlerAdded事件,该事件会触发 pipeline 中添加的 ChannelInitializer 的 handlerAdded 方法,详见: handlerAdded事件 小节。
line:34 -> ChannelInitializer 的 initChannel 方法中,会向 pipeline 添加自定义的 Handler,由 serverBootstrap.handler 方法配置,基于 示例代码(点击跳转),此处是 LoggingHandler。
line:40 -> 向 NioServerSocketChannel 所属的 NioEventLoop 提交一个 Runnable 异步任务。
这个 Runnable 异步任务的作用就是在 NioServerSocketChannel 的 pipeline 中添加一个 ServerBootstrapAcceptor 处理器。
ServerBootstrapAcceptor 是一个特殊的入站处理器,它的作用就是当建立新的 SocketChannel 连接时,将 SocketChannel 注册到 childGroup 中的某个 NioEventLoop 的 Selector(多路复用器)[1] 上。
这里可能会有个问题,为什么不干脆直接调用 ChannelPipeline#addLast(io.netty.channel.ChannelHandler...)
方法,直接将 ChannelHandler 添加到 pipeline 中,而是又使用到了 ChannelInitializer 呢?
初始化 NioServerSocketChannel 中 pipeline 的动作,需要等到 NioServerSocketChannel 注册到 parentGroup 中某个 NioEventLoop 的 Selector(多路复用器)[1] 上以后才可以进行初始化,当前只是创建好 NioServerSocketChannel,并未注册完成。
2.2.1. 流程图
初始化 NioServerSocketChannel 流程如下图:
2.3. 注册Channel
以上是 NioServerSocketChannel 的创建及初始化,下面就是将 NioServerSocketChannel 注册到 parentGroup 中的某个 NioEventLoop 的 Selector(多路复用器)[1] 上。
1 | // io.netty.bootstrap.AbstractBootstrap#initAndRegister |
- line: 10 ->
config().group()
方法返回的是 parentGroup,实际调用的是io.netty.channel.MultithreadEventLoopGroup#register
方法。
2.3.1. 选择EventLoop
接上,MultithreadEventLoopGroup#register
相关源码如下:
1 | // io.netty.channel.MultithreadEventLoopGroup#register(io.netty.channel.Channel) |
MultithreadEventLoopGroup#register
方法中会调用 next 方法,next 方法一路向上调用,最后调用到 chooser.next
方法。chooser 类似负载均衡器,用于从 parentGroup 中选取一个 NioEventLoop,然后将 NioServerSocketChannel 注册到其 Selector(多路复用器)[1] 上。
关于chooser,在 Netty_NioEventLoop创建 的「创建chooser」一节中有简略分析,此处不再赘述。
而 next 方法最终返回的是 NioEventLoop,因此这里实际调用的是 SingleThreadEventLoop#register
方法。
2.3.2. 注册前的准备
通过上面 next 方法,使用 chooser 从 parentGroup 中选取出一个 NioEventLoop 后,然后调用 NioEventLoop 的 register 方法。
在向上逐层调用 NioEventLoop 父类的 register 方法过程中,将 NioServerSocketChannel 和 NioEventLoop 包装成了 DefaultChannelPromise。
1 | // io.netty.channel.MultithreadEventLoopGroup#register(io.netty.channel.Channel) |
- line:8 -> DefaultChannelPromise 中的 channel 属性是 NioServerSocketChannel,executor 属性是 NioEventLoop。
- line:14 -> 调用
AbstractChannel.AbstractUnsafe#register
方法,将 NioServerSocketChannel 注册到 NioEventLoop 的 Selector(多路复用器)[1] 上。
1 | // io.netty.channel.AbstractChannel.AbstractUnsafe#register |
上面这段代码,在 Netty_NioEventLoop启动和运行 的「NioEventLoop启动」一节中曾经分析过: NioEventLoop 创建后,内部工作线程并不会立即运行,NioEventLoop 创建内部工作线程的时机是在第一个任务提交时。
当时重点关注的是 line:11 -> eventLoop.execute(...)
方法,而注册逻辑(将 NioServerSocketChannel 注册到 NioEventLoop 的 Selector[1] 上)被包装成 Runnable 放入队列待运行,下面就需要着重关注 register0
方法逻辑。
2.3.3. 注册到EventLoop
register0
方法在 AbstractChannel.AbstractUnsafe 内部抽象类中,可以预见接下来会调用一堆子类实现的方法。
1 | // io.netty.channel.AbstractChannel.AbstractUnsafe#register0 |
line: 6 ->
!promise.setUncancellable()
逻辑是检查 NioServerSocketChannel 的注册动作是否被取消了。!ensureOpen(promise)
逻辑是检查要注册的 NioServerSocketChannel 是否已经被关闭。如果 NioServerSocketChannel 已经关闭或者注册操作已经被取消,那么就直接返回,停止后续注册流程。line: 11 -> 执行真正的注册操作,最终实现在 AbstractChannel 的子类 AbstractNioChannel 中,调用 JDK 层面的 API 将 NioServerSocketChannel 注册到 NioEventLoop 的 Selector(多路复用器)[1] 上。
line: 17 -> 触发 HandlerAdded 事件,回调 pipeline 中添加的 ChannelInitializer 的 handlerAdded 方法,在这里初始化 channelPipeline。
line: 21 -> 设置
regFuture
为 Success,并回调注册在regFuture
上的 ChannelFutureListener#operationComplete 方法,在 operationComplete 回调方法中将绑定操作封装成异步任务,提交到 taskQueue中,等待执行。line: 24 -> 触发 channelRegistered 事件。
line: 32 -> Channel 状态为活跃时,触发 channelActive 事件。
2.3.3.1. doRegister
在 doRegister 方法中调用 JDK 底层 NIO API java.nio.channels.SelectableChannel#register
,将 NioServerSocketChannel 中包装的 JDK NIO 原生 ServerSocketChannel 注册到 Selector(多路复用器)[1] 上。
1 | // io.netty.channel.nio.AbstractNioChannel#doRegister |
line: 8 -> 当前 Channel 是 NioServerSocketChannel,因此
javaChannel()
方法返回的是java.nio.channels.ServerSocketChannel
,详阅:Netty_NioServerSocketChannel与NioSocketChannel 中「NioServerSocketChannel构造函数」一节。line: 8 -> 所谓的 unwrappedSelector 是指被Netty优化过的 JDK NIO 原生 Selector [1],详阅:Netty_NioEventLoop创建 中「优化NIO原生Selector」一节。
line: 8 -> SelectableChannel#register 方法参数的含义:
Selector - 表示 JDK NIO Channel 将要向哪个 Selector[1] 进行注册,即:将
java.nio.channels.ServerSocketChannel
注册到 unwrappedSelector 上。ops -> 表示 Channel 感兴趣的 IO事件,当对应的 IO事件就绪 时, Selector 会返回 Channel 对应的 SelectionKey 。
attachment -> 向 SelectionKey 中添加用户自定义的附加对象。
此处传进去的 this 是 Netty 自己的 Channel,也就是 NioServerSocketChannel,这步操作非常巧妙。之前可能会疑惑,Selector 每次返回的是 jdk 底层的 Channel,那么 Netty 是怎么知道它对应哪个 Netty Channel 的呢?
这里找到了答案:通过attachment
属性,完成 Netty 自定义 Channel(也就是 NioServerSocketChannel) 与JDK NIO Channel 的关系绑定。这样每次对 Selector(多路复用器) 进行 I/O 就绪事件轮询 时,Netty 都可以从 Selector 返回的 SelectionKey 中获取到自定义的 Channel 对象(NioServerSocketChannel)。
javaChannel().register()
方法需要指定监听的网络操作位来表示 Channel 对哪几类网络事件感兴趣,定义如下:
- 读 - public static final int OP_READ = 1 << 0;
- 写 - public static final int OP_WRITE = 1 << 2;
- 客户端连接 - public static final int OP_CONNECT = 1 << 3;
- 服务端连接 - public static final int OP_ACCEPT = 1 << 4;
如上源码示例中,op传值是0,代表对任何事件都不感兴趣,仅为了完成注册操作。
完成 NioServerSocketChannel 注册后,会触发 NioServerSocketChannel 上 ChannelPipeline 的 handlerAdded 和 channelRegistered 两个事件。
2.3.3.2. handlerAdded事件
pipeline.invokeHandlerAddedIfNeeded();
方法触发 handlerAdded 事件,然后回调 pipeline 中添加的 ChannelInitializer 的 handlerAdded 方法,即:io.netty.channel.ChannelInitializer#handlerAdded
。
本篇中有两处 ChannelInitializer ,一处是源码内部 初始化 NioServerSocketChannel(点击跳转),另一处是 示例代码(点击跳转) 中通过 serverBootstrap.handler(new LoggingHandler(LogLevel.INFO))
方法添加 Handler。
回顾 示例代码(点击跳转) ,通过 serverBootstrap.handler(new LoggingHandler(LogLevel.INFO))
方法添加 Handler,如果想添加多个处理器怎么办?只需要传入一个 ChannelInitializer,然后在 initChannel() 方法中添加处理器就行,就像下面这样:
1 | serverBootstrap |
这是日常开发中使用比较多的写法,为什么在 ChannelInitializer 的 initChannel 方法中可以为 pipeline 添加 Handler?答案正是在 ChannelInitializer 的 handlerAdded 事件回调中。
ChannelInitializer 会先调用用户实现类的 initChannel 方法添加用户的 Handler,然后把自己(即:ChannelInitializer)从 pipeline 移出。
1 | // io.netty.channel.ChannelInitializer |
line: 4 ->
ctx.channel().isRegistered()
逻辑判断当前 Channel(NioServerSocketChannel) 是否已经完成注册,只有 Channel 注册完成后才可以进行 pipeline 的初始化。line: 5 -> 调用 initChannel 方法,准备执行初始化 pipeline 逻辑。
line: 16 -> 调用 ChannelInitializer 匿名类的 initChannel 方法执行自定义的初始化逻辑。
line: 7 ->
pipeline.remove(this)
逻辑,当执行完 line: 5 -> initChannel 方法后,pipeline 的初始化就结束了,此时 ChannelInitializer 就没必要再继续呆在 pipeline 中了,因此将 ChannelInitializer 从 pipeline 中删除。
在 初始化NioServerSocketChannel(点击跳转) 中就曾经向 NioServerSocketChannel 的 pipeline 中添加 ServerBootstrapAcceptor,结合到一起用图表示如下:
2.3.3.3. safeSetSuccess
在 AbstractChannel.AbstractUnsafe#safeSetSuccess
方法中,回调注册在regFuture
上的 ChannelFutureListener,在 ChannelFutureListener 的回调函数中开始发起绑端口地址流程。
2.4. 流程图
注册 NioServerSocketChannel 的流程如下图:
3. doBind0
initAndRegister(点击跳转) 小节中分析了 创建、初始化 NioServerSocketChannel,并将 NioServerSocketChannel 异步注册到 parentGroup 中某个 NioEventLoop 的 Selector(多路复用器)[1] 上的过程。
值得注意的是,以上整个过程中,注册的过程是异步的,所以在 initAndRegister
方法调用后返回一个代表注册结果的 regFuture ChannelFuture。
1 | // io.netty.bootstrap.AbstractBootstrap#doBind |
之后会向 ChannelFuture regFuture
添加一个回调函数(ChannelFutureListener),在注册完成后执行。在回调函数中开始发起绑端口地址流程。
在将 NioServerSocketChanne 注册到 parentGroup 的过程中,即: safeSetSuccess 小节中,回调注册在regFuture
上的 ChannelFutureListener,在 ChannelFutureListener 的回调函数中开始发起绑端口地址流程。
暂不深入研究
1 | // io.netty.channel.AbstractChannel.AbstractUnsafe#bind |
4. 示例代码
1 |
|
如示例代码不合胃口,可以食用官方源码中的Test:
io.netty.bootstrap.ServerBootstrapTest#testHandlerRegister
。
5. 流程图
Netty ServerBootstrap 启动完整流程图如下图:
6. Reference
- 1.java.nio.channels.Selector(多路复用器) 可以看成是操作系统底层中 select/epoll/poll 多路复用函数的Java包装类。只要 Selector 监听的任一一个 Channel 上有事件发生,就可以通过 Selector.select() 方法监测到,并且使用 SelectedKeys 可以得到对应的 Channel,然后对它们执行相应的I/O操作。 ↩