Netty 服务端启动代码基本如下所示,服务端启动时 Netty 内部为我们做了什么?
- 服务端的线程模型是如何设置的?
- 服务端的 channel 是如何创建并初始化的?
- 具体又是怎么绑定端口后监听客户端连接请求的?
1 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); |
核心概念
ServerBootstrap
作为抽象辅助类 AbstractBootstrap 的子类,ServerBootstrap 主要提供给服务端使用;
基于此进行一些 group、channel、option、handler、childOption 和 childHandler 的配置,其中 option 和 handler 是针对自身的,而 childOption 和 childHandler 是针对后面接收的每个连接的;
NioServerSocketChannel
封装了 jdk 的 ServerSocketChannel,且存在一个属性 readInterestOp 记录对什么事件感兴趣,初始化时为
SelectionKey.OP_ACCEPT
;
以及继承自父类 AbstractChannel 的三大属性 id(全局唯一标识)、unsafe(依附于 channel,负责处理操作) 和 pipeline(责任链模式处理请求);Unsafe
Unsafe 附属于 Channel,大部分情况下仅在内部使用;
ChannelPipeline
初始化 DefaultChannelPipeline 时,设置 HeadContext、TailContext 两个节点组成双向链表结构,两个节点均继承自 AbstractChannelHandlerContext;
换句话说 ChannelPipeline 就是一个双向链表 (head ⇄ handlerA ⇄ handlerB ⇄ tail),链表的每个节点是一个 AbstractChannelHandlerContext;
NioEventLoopGroup
有个属性 EventExecutor[],而下面要说的 NioEventLoop 就是 EventExecutor;
按 Reactor (主从)多线程模型来说,服务端存在两个 NioEventLoopGroup,一个 NioEventLoopGroup(bossGroup) 用来接收连接,然后将连接丢给另一个 NioEventLoopGroup(workerGroup),
以后该连接的读写事件均有这个 workerGroup 的一个 NioEventLoop 来执行;NioEventLoop
核心线程(其实看源码的话,好像理解成一个 task 更合理),含有重要属性 executor、taskQueue、selector 等;
创建 NioEventLoopGroup
创建 NioEventLoopGroup 的时候最终调用其父类 MultithreadEventExecutorGroup 的构造方法,主要代码如下:
1 | protected MultithreadEventExecutorGroup(int nThreads, Executor executor, |
创建 NioEventLoopGroup 主要做了以下几件事:
- 没有 executor 则创建一个 executor
- 初始化一个 EventExecutor[],为创建 NioEventLoop 做准备
- 循环调用 newChild 方法(传入 executor )去创建 NioEventLoop 对象,置于 EventExecutor[] 数组当中
- 创建一个 chooser,用来从 EventExecutor[] 选取处一个 NioEventLoop
创建 NioEventLoop
创建 NioEventLoop 的时候会调用其父类 SingleThreadEventExecutor 的构造方法,主要代码如下:
1 | protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor, |
创建 NioEventLoop 主要做了以下几件事:
- 创建任务队列,以后调用 NioEventLoop.execute(Runnable task) 的时候均是把 task 放入该任务队列
- 创建一个 selector,之后要把 channel 注册到该 selector 上
设置 ServerBootstrap
设置 Reactor 线程模型
1 | // ServerBootstrap.group(group, group) |
设置 IO 模型
1 | // AbstractBootstrap.channel(channelClass) |
设置 TCP 参数
1 | // AbstractBootstrap.option(option, value) |
设置业务处理 handler
1 | // AbstractBootstrap.handler(handler) |
绑定端口
首先根据端口构造 SocketAddress 对象,然后调用 doBind(localAddress) 方法:
1 | // AbstractBootstrap.doBind(localAddress) |
初始化 channel(NioServerSocketChannel)
1 | // AbstractBootstrap.initAndRegister() |
注册 channel 到 selector
回到 initAndRegister() 方法中,在创建 channel 并调用 init(channel) 方法后,开始把 channel 注册到 selector 上,通过 EventLoopGroup 来注册;
首先我们看下 EventLoopGroup 接口中有哪些方法:
1 | public interface EventLoopGroup extends EventExecutorGroup { |
NioEventLoopGroup 的 register(channel) 方法继承自父类 MultithreadEventLoopGroup
1 | // NioEventLoopGroup.register(channel) |
NioEventLoop 的 register(channel) 方法继承自父类 SingleThreadEventLoop
1 | // NioEventLoop.register(channel) |
AbstractUnsafe 的 register(eventLoop, promise) 相关方法主要代码如下:
1 | // AbstractUnsafe.register(eventLoop, promise) |
ChannelRegistered 事件在 pipeline 上的传播
然后我们来看注册成功后,fireChannelRegistered 事件在 pipeline 上怎么传播:
1 | // DefaultChannelPipeline.fireChannelRegistered() |
如果我们添加了 handlerA、handlerB,那么内部会将其封装成两个 DefaultChannelHandlerContext 并添加到 pipeline 中,此时链表结构为:head ⇄ handlerA ⇄ handlerB ⇄ tail
,ChannelRegistered 事件从 head 节点开始往后传播,在经历了自定义的 ChannelInboundHandler 后,最终会到达 tail 节点,tail 节点对该事件不作处理。
1 | // HeadContext.channelRegistered(ctx) |
绑定端口
回到 AbstractBootstrap.doBind(localAddress) 方法中,可以看到在初始化 channel 并将其注册到 NioEventLoop 的 selector 上后,才开始调用 doBind0 执行真正的绑定动作。
1 | // AbstractBootstrap.doBind0(future, channel, localAddress, promise) |
ChannelActive 事件在 pipeline 上的传播
上面看到在调用 jdk 的 ServerSocketChannel 执行 bind 操作后,将 ChannelActive 事件在 pipeline 上的传播封装成 task 丢给 NioEventLoop 线程去执行了,那么我们接着看 ChannelActive 事件在 pipeline 上的传播是否像 ChannelRegistered 的传播一样呢。
1 | // DefaultChannelPipeline.fireChannelActive() |
服务端启动总结
首先创建两个 NioEventLoopGroup,一个 bossGroup 用于接收客户端的连接请求;一个 workerGroup 用于处理连接的 IO 读写操作;每个 NioEventLoopGroup 持有一个 EventExecutor[] 数组,用来保存 NioEventLoop
创建 NioEventLoop 对象填充 NioEventLoopGroup 的 EventExecutor[] 数组;每个 NioEventLoop 都有自己的 executor、taskQueue、selector 等属性
创建辅助类 ServerBootstrap,并设置 Reactor 线程模型、IO 模型、TCP 参数以及业务处理 handler 等
调用 ServerBootstrap 的 bind 方法进行端口绑定
反射创建一个 NioServerSocketChannel 对象,并进行初始化(设置一些属性后,在 pipeline 后添加一个 ChannelInitializer,重写的 initChannel 方法在 handlerAdded 方法中调用)
从 NioEventLoopGroup 选出一个 NioEventLoop,将 channel 注册到它的 selector 上,最终调用的是 jdk 的 ServerSocketChannel 的注册方法,但是 ops 为 0
注册成功后,进行 handlerAdded、ChannelRegistered 等事件的触发
然后开始真正的绑定端口,最终还是调用 jdk 的 ServerSocketChannel 的 bind 方法
进行 ChannelActive 事件的传播,其中会修改 channel 注册到 selector 上返回的 selectionKey 的 ops 为 SelectionKey.OP_ACCEPT,以便用来监听客户端的连接
- 一句话总结:初始化 channel 后,注册到 selector 上,触发 handlerAdded、ChannelRegistered 事件后绑定端口,接着触发 ChannelActive 事件,其中又会修改 selectionKey 的 ops 以便监听客户端的连接