在使用Netty进行服务端程序开发时,主要涉及端口监听、EventLoop线程池创建、NioServerSocketChannel和 ClannelPipeline初始化等。
netty是一款基于NIO的高性能、异步事件驱动的网络程序框架,它对JDK中的NIO做了封装和优化,提高了性能的同时降低了使用的难度。
作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的。
Netty是对NIO的封装,通过ChannelPipeline责任链实现,将InBound和outBound执行过程。
我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。
Netty的启动流程中,涉及到多个操作,比如register、bind、处理对应事件等,为了不影响main线程执行,这些工作以task的形式提交给NioEventLoop,由NioEventLoop来执行这些task。
- 创建ServerBootstrap实例,设置启动参数group(boss、worker)、option、设置channel通道类型NioServerSocketChannel、绑定端口、启动服务。
- 设置channel通道类型:指定使用NioServerSocketChannel来建立请求连接。
- 创建server对应的channel,创建各大组件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等。
- 构造一系列channelHandler处理链来组成ChannelPipeline。ChannelPipeline控制着ChannelHandler的流转。
- option用来配置一些channel的参数,配置的参数会被ChannelConfig使用。
- 编写的TestServerHandler可以继承ChannelInboundHandlerAdapter,复写了channelRead()和exceptionCaught()方法。
- 初始化channel,设置attr及options等,给channel添加接入器,触发register事件,调用handler方法中绑定的channelhandler的channelActive、channelAdd、channelRegister等方法。
- 绑定监听端口并启动服务端,select()轮询。I/O读写等网络事件通知。自定义ChannelHandler的调度和执行。
(调用到jdk底层做端口绑定,并触发active事件,active触发的时候,真正做服务端口绑定)
EventLoopGroup是基于JDK的线程池进行封装的实现,它包含一组 EventLoop,Channel 通过注册到 EventLoop的Selector 中执行操作。
EventLoopGroup创建NioEventLoopGroup就是创建了2个线程池boss和worker。boss负责接收连接请求;worker负责处理具体的IO操作等,初始线程池大小默认是CPU核数* 2。
SingleThreadEventExecutor 是Netty 中对本地线程的抽象,它内部有一个priavate static volatile Thread thread 属性,存储了一个本地Java线程。因此我们可以简单地认为,一个NioEventLoop 其实就是和一个特定的线程绑定,并且在其生命周期内,绑定的线程都不会再改变。
NioEventLoopGroup
NioEventLoopGroup 是一个基于Reactor模型的线程池循环处理器,是一个多线程事件驱动IO操作类。
NioEventLoopGroup 初始化的基本过程:
- EventLoopGroup(其实是MultithreadEventExecutorGroup)内部维护一个类为EventExecutor[] children 数组,其大小是nThreads;
- 在MultithreadEventExecutorGroup 中会调用newChild()抽象方法来初始化children 数组;
- 在NioEventLoopGroup 中具体实现newChild()方法,该方法返回一个NioEventLoop 实例。
- 初始化NioEventLoop 主要属性:
4.1 provider:在NioEventLoopGroup 构造器中通过SelectorProvider 的provider()方法获取SelectorProvider。
4.2 selector:在NioEventLoop 构造器中调用selector = provider.openSelector()方法获取Selector 对象。
NioEventLoop
NioEventLoop 的继承关系:
NioEventLoop->SingleThreadEventLoop->SingleThreadEventExecutor->AbstractScheduledEventExecutor->实现 EventLoop 接口。
EventLoop 任务执行者
NioEventLoop(准确来说是SingleThreadEventExecutor)中包含了private volatile Thread thread,该thread变量的初始化是在new的线程第一次执行run方式时才赋值的,这种形式挺新颖的。
SingleThreadEventExecutor 是Netty 中对本地线程的抽象,它内部有一个priavate volatile Thread thread 属性,存储了一个本地Java线程。因此我们可以简单地认为,一个NioEventLoop 其实就是和一个特定的线程绑定,并且在其生命周期内,绑定的线程都不会再改变。
在AbstractScheduledEventExecutor 中, Netty 实现了NioEventLoop 的schedule 功能,即我们可以通过调用一个NioEventLoop 实例的schedule 方法来运行一些定时任务。而在SingleThreadEventLoop 中,也实现了任务队列的功能,通过它,我们可以调用一个NioEventLoop 实例的execute()方法来向任务队列中添加一个task,并由NioEventLoop进行调度执行。
一个EventLoop对应一个线程,其内部包含一个FIFO的taskQueue和Selector,负责处理客户端请求和内部任务,内部任务如ServerSocketChannel注册和ServerSocket绑定操作等。
NioEventLoop 创建时就会初始化一个Reactor,包括selector和taskQueue。
它负责两件事:
第一个作为IO 线程,执行与Channel 相关的IO 操作,包括调用Selector 等待就绪的IO 事件、读写数据与数据的处理等;
第二个作为任务队列,执行taskQueue 中的任务,例如用户调用eventLoop.schedule 提交的定时任务也是这个线程执行的。
NioEventLoop 具有执行 IO 和业务操作的能力,通常情况为了避免 IO 和业务处理互相影响,添加 handler 会指定线程池。
//
NioEventLoop 的实例化过程
SingleThreadEventExecutor 启动时会调用doStartThread()方法,然后调用executor.execute()方法,将当前线程赋值给thread。在这个线程中所做的主要就是调用SingleThreadEventExecutor.this.run()方法,而因为NioEventLoop 实现了这个方法,其实调用的是NioEventLoop.run()方法。
Selector
Selector是一个多路复用器,它负责管理被注册到其上的SelectableChannel。Selector的实现根据操作系统的不同而不同,目前多路复用IO实现主要包括四种:select、poll、epoll、kqueue。
SelectorProvider不同操作系统的I/O多路复用选择器各自内核实现不同,目前有select、poll、epoll、kqueue四种实现,JDK里与之对应的实现也随着操作系统的不同而不同。
总结一句话
当EventLoop 的execute()第一次被调用时,就会触发startThread()方法的调用,进而导致EventLoop所对应的Java 本地线程启动。
EventLoopGroup创建完成后,启动的第一步就算完成了,接下来该进行bind、listen操作了。
ServerBootstrap的bind流程涉及到NioChannel的创建、初始化和注册(到Selector),启动NioEventLoop,之后就可以对外提供服务了。
这里涉及到2个操作,一个是channel的创建、初始化、注册;另一个是bind操作。
channel 创建、初始化、注册、绑定操作
初始化channel,将channel、childHandler的参数设置到对应对象上,然后将childHandler添加到channel的pipleline中。
初始化channel流程,主要操作是设置channel属性、设置channel.pipeline的ChannelInitializer,注意,ChannelInitializer是在channel注册到selector之后被回调的。
channel初始化之后就该将其注册到selector,即下面的register流程:
bind操作是在register之后进行的,因为register0()方法是由NioEventLoop执行的,所以main线程需要先判断下future是否完成,如果完成直接进行doBind即可,否则添加listener回调进行doBind。
bind操作及后续初始化操作(channelActive回调、设置监听事件)
ChannelPipeline、ChannelHandler、ChannelHandlerContext 三者关系
一个ChannelPipeline中可以有多个ChannelHandler实例,而每一个ChannelHandler实例与ChannelPipeline之间的桥梁就是ChannelHandlerContext实例。
ChannelHandlerContext里就包含着ChannelHandler中的上下文信息,每一个ChannelHandler被添加都ChannelPipeline中都会创建一个与其对应的ChannelHandlerContext。ChannelHandlerContext的功能就是用来管理它所关联的ChannelHandler和在同一个ChannelPipeline中ChannelHandler的交互。
initAndRegister() 初始化和注册
首先Channel创建、初始化、注册,由于注册是一个异步的过程,所以initAndRegister()方法返回的是一个ChannelFuture。
在initAndRegister()方法中先通过反射创建了一个NioServerSocketChannel实例,接着调用init()方法初始化该NioServerSocketChannel对象。
将channel注册到执行当前操作的eventLoop的多路复用器上,并且将channel设置为附件,注册的操作为0。也就是不关心任何操作。
初始化channel流程
初始化channel流程,主要操作是设置channel属性、设置channel.pipeline的ChannelInitializer。
将channel、childHandler的参数设置到对应对象上,然后将childHandler添加到channel的pipleline中。
注意,ChannelInitializer是在channel注册到Selector之后被回调的。
init()方法
init()方法最后一段代码,它注册了一个ChannelInitializer到Channelpipline上,该ChannelInitializer的initChannel()方法创建了一个ServerBootstrapAcceptor注册到channel的ChannelPipeline中,设置对外监听。
注册
将channel注册到执行当前操作的eventLoop的多路复用器上,并且将channel设置为附件,注册的操作为0。也就是不关心任何操作。
注册完成后, netty 会触发一个invokeHandlerAddedIfNeeded()方法, 从而调用fireHandlerAdded()将首次调用 ChannelInitializer.initChannel(), 将 ServerBootstrapAcceptor 添加到pipeline进来。
绑定
doBind方法是真正执行绑定操作的方法,是调用java的的ServerSocket#bind()方法。
到这里为止整个netty启动流程就基本接近尾声,可以对外提供服务了。
Netty的启动流程中,涉及到多个操作,比如register、bind、注册对应事件等,为了不影响main线程执行,这些工作以task的形式提交给NioEventLoop,由NioEventLoop来执行这些task,也就是register、bind、注册事件等操作。
Netty启动流程图如下所示:
Netty的启动流程,就是创建NioEventLoopGroup和实例化ServerBootstrap实例,并进行bind的过程。
public static void main(String[] args)
{
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap server = new ServerBootstrap();//启动类
server.group(boss, worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new TestServerHandler());
}
});
ChannelFuture cf = server.bind(9090).sync();
cf.channel().closeFuture().sync();
}
catch(Exception e) {
e.printStackTrace();
}finally {
logger.info("server shutdown.");
boss.shutdownGracefully();
worker.shutdownGracefully();
}}
EventLoopGroup中会包含了多个EventLoop,EventLoop是一个Reactor模型的事件处理器。
一个EventLoop对应一个线程,其内部会维护一个taskQueue和Selector,负责处理客户端请求和内部任务,内部任务如ServerSocketChannel注册和ServerSocket绑定操作等。
NioEventLoopGroup.class
NioEventLoopGroup 初始化的时序图
基本步骤如下:
EventLoopGroup创建多个NioEventLoop,这里创建NioEventLoop就是初始化一个Reactor,包括创建taskQueue和初始化Selector。
NioEventLoop架构图
NioEventLoop 的类层次结构图还是有些复杂的,不过我们只需要关注几个重要点即可。
NioEventLoop 的继承关系:
NioEventLoop->SingleThreadEventLoop->SingleThreadEventExecutor->AbstractScheduledEventExecutor->实现EventLoop接口。
在AbstractScheduledEventExecutor 中, Netty 实现了NioEventLoop 的schedule 功能,即我们可以通过调用一个NioEventLoop 实例的schedule 方法来运行一些定时任务。而在SingleThreadEventLoop 中,又实现了任务队列的功能,通过它,我们可以调用一个NioEventLoop 实例的execute()方法来向任务队列中添加一个task,并由NioEventLoop进行调度执行。
NioEventLoop 负责两件事:
主要逻辑如下:
public final class NioEventLoop extends SingleThreadEventLoop {
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
// 创建taskQueue
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
// 是不是很熟悉,java nio selector操作
provider = selectorProvider;
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
}}
Selector是一个多路复用器,它负责管理被注册到其上的SelectableChannel。Selector的实现根据操作系统的不同而不同,目前多路复用IO实现主要包括四种:select、poll、epoll、kqueue。
SelectorProvider不同操作系统的I/O多路复用选择器各自内核实现不同,目前有select、poll、epoll、kqueue四种实现,JDK里与之对应的实现也随着操作系统的不同而不同。
新连接接入通过chooser绑定一个NioEventLoop
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args){
// 创建NioEventLoop实例
this.children = new EventExecutor[nThreads];
// 初始化NioEventLoop,实际调用的是NioEventLoopGroup.newChild方法
for (int i = 0; i < nThreads; i ++) {
children[i] = newChild(executor, args);
}
// 多个NioEventLoop中选择策略
chooser = chooserFactory.newChooser(children);
}
SingleThreadEventExecutor 启动时会调用doStartThread()方法,然后调用executor.execute()方法,将当前线程赋值给thread。在这个线程中所做的主要就是调用SingleThreadEventExecutor.this.run()方法,而因为NioEventLoop 实现了这个方法,其实调用的是NioEventLoop.run()方法。
NioEventLoop 继承自SingleThreadEventLoop,而SingleThreadEventLoop 又继承自SingleThreadEventExecutor。而SingleThreadEventExecutor 是Netty 中对本地线程的抽象,它内部有一个Thread thread 属性,存储了一个本地Java线程。因此我们可以简单地认为,一个NioEventLoop 其实就是和一个特定的线程绑定,并且在其生命周期内,绑定的线程都不会再改变。
EventLoopGroup创建完成后,启动的第一步就算完成了,接下来该进行bind、listen操作了。
server端启动流程可以理解成创建ServerBootstrap实例的过程
ServerBootstrap 和 BootStrap 均继承AbstractBootStrap,这里AbstractBootStrap成员变量group对应bossgroup,负责处理客户端认证,ServerBootStrap中的成员变量childGroup即为线程模型中的workGroup线程池,负责处理认证成功的IO事件。其他参数也对应着bossgroup和workgroup中相应设置。
类名 | ServerBootStrap | AbstractBootStrap |
---|---|---|
Channel连接配置 | childOptions | options |
参数 | childAttrs | Attrs |
线程组 | childGroup | group |
业务处理 | childHandler | handler |
设置通道类型NioServerSocketChannel.class,通过反射
//AbstractBootstrap.class
public B channel(Class<? extends C> channelClass) {
return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory((Class)ObjectUtil.checkNotNull(channelClass, "channelClass"))));
}
public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
return this.channelFactory((ChannelFactory)channelFactory);
}
@Deprecated
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
ObjectUtil.checkNotNull(channelFactory, "channelFactory");
if (this.channelFactory != null) {
throw new IllegalStateException("channelFactory set already");
} else {
this.channelFactory = channelFactory;
return this.self();
}
}
这里涉及到2个操作,一个是Channel的创建、初始化、注册操作,另一个是bind操作。
bind操作是ServerBootstrap流程重要的一环,这里是对NIO的封装,bind流程涉及到NioChannel的创建、初始化和注册(到Selector),启动NioEventLoop,之后就可以对外提供服务了。
注意,这里如果main线程执行到regFuture.isDone()时,register还未完成,那么main线程是不会直接调用bind操作的,而是往regFuture上注册一个Listenner,这样channel register完成(注册到Selector并且调用了invokeHandlerAddedIfNeeded)之后,会调用safeSetSuccess,触发各个ChannelFutureListener,最终会调用到这里的operationComplete方法,进而在执行bind操作。
首先Channel创建、初始化、注册,由于注册是一个异步的过程,所以initAndRegister()方法返回的是一个ChannelFuture。
在initAndRegister()方法中先通过反射创建了一个NioServerSocketChannel实例,接着调用init()方法初始化该NioServerSocketChannel对象。
初始化channel流程,主要操作是设置channel属性、设置channel.pipeline的ChannelInitializer。
将channel、childHandler的参数设置到对应对象上,然后将childHandler添加到channel的pipleline中。
注意,ChannelInitializer是在channel注册到Selector之后被回调的。
前面都是在初始化属性,重要的最后一段代码,它注册了一个ChannelInitializer到channelpipline上,该ChannelInitializer的initChannel方法创建了一个ServerBootstrapAcceptor注册到channel上,这个ServerBootstrapAcceptor会在服务器处理连接请求时使用。
this.init(Channel channel) 源码如下:
ChannelPipeline p = channel.pipeline();
head和tail是同一个对象,addLast()是一个双向链表的添加。
/**
* 初始channel属性,也就是ChannelOption对应socket的各种属性。
* 比如 SO_KEEPALIVE SO_RCVBUF ... 可以与Linux中的setsockopt函数对应起来。
* 最后将ServerBootstrapAcceptor添加到对应channel的ChannelPipeline中。
*/
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
ChannelPipeline p = channel.pipeline();
// 获取childGroup和childHandler,传递给ServerBootstrapAcceptor
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
}
p.addLast(new ChannelInitializer<Channel>() {
//在register0中,将channel注册到Selector之后,会调用invokeHandlerAddedIfNeeded,
//进而调用到这里的initChannel方法
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
// 这里注册一个添加ServerBootstrapAcceptor的任务
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
// 添加ServerBootstrapAcceptor
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
调用了 initChannel 方法。这里调用的并不是我们重写的 initChannel 方法,因为参数不是同一个类型,是ChannelHandlerContext,所以还需要再调用一层。
再回到init()方法,将ServerBootstrapAcceptor添加到对应channel的ChannelPipeline中,设置对外监听。
ServerBootstrapAcceptor 构造方法
addLast()
this.callHandlerAdded0(newCtx);
callHandlerAdded()
pipeline.fireChannelRegistered()
pipeline.fireChannelRegistered() 的方法。按照之前的 pipeline 的路子想一下,会如何执行?pipeline 作为管道,其中有我们设置的 handler 链表,这里肯定会顺序执行 main 方法中的 childerHandler。
该方法会继续调用 Context 的 fireChannelRegistered 方法,Context 包装的就是我们自定义的 handler。当然我们没有重写该方法。我们只重写了 initChannel 方法。
【注册channel,绑定eventloop线程】
经过前面两步, channel已经创建好和初始化好了, 但还没有看到 eventLoop 的影子. 实际上eventloop和channel间就差一个注册了.
回到initAndRegister()方法,channel初始化之后就该将其注册到Selector,即下面的register流程:
ChannelFuture regFuture = config().group().register(channel); //此处的group 即 是 bossGroup
MultithreadEventLoopGroup.class
注册channel,绑定eventloop线程。
注册channel 的过程中,最终会在AbstractChannel$AbstractUnsafe的
register()方法中调用eventLoop.execute()方法,在EventLoop 中进行Channel 注册代码的执行。
开始真正的异步,线程开始启动。
如果是 boss 线程,而不是 worder 线程,所以eventLoop.inEventLoop()肯定无法通过判断。
先判断当前线程是否是线程池的线程,如果是则直接执行注册方法,否则提交任务到线程池。为什么要这样做呢?
《Netty权威指南 第二版》中是这样说到——首先判断是否是NioEventLoop自身发起的操作。如果是,则不存在并发操作,直接执行Channel注册;如果由其他线程发起,则封装成一个Task放入消息队列中异步执行。此处,由于是由ServerBootstrap所在线程执行的注册操作,所以会将其封装成Task投递到NioEventLoop中执行。
从Bootstrap 的bind()方法跟踪到AbstractChannel$AbstractUnsafe
的register()方法,整个代码都是在主线程中运行的,因此eventLoop.inEventLoop()返回为false,于是进入到else 分支,调用eventLoop.execute()方法。
而NioEventLoop 没有实现execute()方法,因此调用的是SingleThreadEventExecutor 的execute()方法:inEventLoop == false,因此执行到else 分支,在这里就调用startThread() 方法来启动SingleThreadEventExecutor 内部关联的Java 本地线程了。
当调用AbstractChannel$AbstractUnsafe.register()
方法后, 会将一个EventLoop 赋值给AbstractChannel 内部的eventLoop 字段,这句代码就是完成EventLoop 与Channel 的关联过程。
总结:当EventLoop 的execute()第一次被调用时,就会触发startThread()方法的调用,进而导致EventLoop所对应的Java 本地线程启动。
register0()方法是由NioEventLoop执行的。进入到异步线程中查看 register0 方法。
AbstractNioChannel.class
将channel注册到执行当前操作的eventLoop的多路复用器上,并且将channel设置为附件,注册的操作为0。也就是不关心任何操作。
《Netty权威指南 第二版》中是这样说到——注册方法是多台的,它既可以被NioServerSocketChannel用来监听客户端的连接接入,也可以注册socketChannel用来监听网络读或者写操作。
那么什么时候会将操作设置为正确的值呢?
小结:回到 register0 方法中,该方法在成功注册到 selector 的读事件后,继续执行管道中可能存在的任务。
doRegister() 操作之后伴随着多个回调及listener的触发:
pipeline.invokeHandlerAddedIfNeeded()
pipeline.fireChannelActive() //触发通道激活
// AbstractChannel$AbstractUnsafe
private void register0(ChannelPromise promise) {
boolean firstRegistration = neverRegistered;
// 这里调用的是AbstractNioChannel.doRegister
// 这里将channel注册上去,并没有关注对应的事件(read/write事件)
doRegister();
neverRegistered = false;
registered = true;
// 调用handlerAdd事件,这里就会调用initChannel方法,设置channel.pipeline,也就是添加 ServerBootstrapAcceptor
pipeline.invokeHandlerAddedIfNeeded();
// 调用operationComplete回调
safeSetSuccess(promise);
// 回调fireChannelRegistered
pipeline.fireChannelRegistered();
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) {
if (firstRegistration) {
// 回调fireChannelActive
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
}
注册完成后, netty 会触发一个invokeHandlerAddedIfNeeded()方法, 从而调用fireHandlerAdded()将首次调用 ChannelInitializer.initChannel(), 将 ServerBootstrapAcceptor 添加到pipeline进来。
这个 pendingHandlerCallbackHead 变量来自我们 addLast 的时候,如果该 pipeline 还没有注册到这个 eventLoop 上,则将这个包装过 handler 的 context 放进变量 pendingHandlerCallbackHead 中,事实上,这个 pendingHandlerCallbackHead 就是个链表的表头,后面的 Context 会被包装成一个任务,追加到链表的尾部。
bind操作是在register之后进行的,因为register0是由NioEventLoop执行的,所以main线程需要先判断下future是否完成,如果完成直接进行doBind即可,否则添加listener回调进行doBind。
将绑定操作提交到线程池中,这样做的原因与注册操作是一样的。
AbstractChannel.this.doBind()
doBind方法是真正执行绑定操作的方法,是调用java的的ServerSocket#bind()方法。
到这里为止整个netty启动流程就基本接近尾声,可以对外提供服务了。
绑定成功之后判断如果是第一次注册,则通知channelActive事件(channelActive回调、设置监听事件)。
通知完channelActive事件后会进行判断,channel是否是自动读,该值默认为true,所以会默认调用channel.read方法,该方法最终会调用AbstractNioUnsafe#doBeginRead()方法
触发childGroup把客户端child中的channel执行操作,这是Netty监听器实现核心原理。
Netty服务端启动流程
https://www.cnblogs.com/ouhaitao/p/12876027.html
Netty之EventLoop
https://www.cnblogs.com/wuzhenzhao/p/11221189.html
Netty 接受请求过程源码分析
https://www.cnblogs.com/stateis0/p/9062141.html