本篇我们看一下Netty服务端监听端口的过程。
ChannelFuture future = bootstrap.bind(9081).sync();
通过ServerBootstrap提供的bind()方法,绑定一个监听端口,依次跟进方法可以看到,最终调用的是ServerBootstrap的父类AbstractBootstrap中的doBind()方法。
继续跟进doBind()方法,首先调用了initAndRegister(),这个方法中,我们可以看到在前面的篇章中讲到过的,通过NioServerSocketChannel的构造器创建新的channel,然后调用子类实现的init()方法,初始化该channel,初始化的过程,除了设置在option、childOption等属性外,最重要的是在NioServerSocketChannel的pipeline上,添加了ServerBootstrapAcceptor,ServerBootstrapAcceptor是一个ChannelInboundHandler,也就是我们之前介绍过的Netty的Reactor线程模型中的acceptor,主要负责监听客户端连接的OP_ACCEPT事件。有一点需要注意的是,initChannel方法,在这里并不会执行,那它是什么时候执行的呢?我们先继续往下看。
NioServerSocketChannel初始化完成之后,就要执行注册的流程了。在看源码的过程中,因为都是面向接口编程,所以很多时候我们看到的并不是实现类,所以要想搞清楚具体的实现类是哪个,可以将代码执行起来看具体的运行时的结果,也可以通过方法名称合理猜测。例如下面的代码,我们可以猜测register方法是调用的MultithreadEventLoopGroup中的方法
config().group().register(channel)
继续看register()方法具体实现
@Override
public ChannelFuture register(Channel channel){
return next().register(channel);
}
next()方法是调用了MultithreadEventExecutorGroup中的实现,从bossGroup的线程池里,选择一个executor,这里也会分两种情况,一种是bossGroup指定的线程数量,是2的整数次幂,另外一种是数量不是2的整数次幂,实现方式,都是轮询。
register的最终实现,在AbstractChannel中,将register0包装成一个Runnable,使用SingleThreadEventExecutor来执行,这里是重点!!
继续跟进eventLoop.execte这个方法
@Override
public void execute(Runnable task) {
ObjectUtil.checkNotNull(task, "task");
execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
}
到这里,一定要记住,task,是在AbstractChannel中包装的register0这个注册任务。先将任务加入到了任务队列中,继续执行startThread()方法。
调用doStartThread()方法启动新的线程执行任务。
在doStartThread()方法中,又封装了一个任务,这个任务中封装的是SingleThreadEventExecutor.this.run(),这里执行任务的executor是ThreadExecutorMap的实例,将任务绑定了eventExecutor,封装成一个新的任务。
看到这里,你的心里是不是有很多小问号?我是谁?我在哪?怎么这么多任务套任务?其实要想搞懂Netty的线程模型,必须要不停的debug,打断点,一步步的跟踪执行,才能够拨开迷雾看清现实。
在这个绑定过程中,为啥只看到了包装任务,没有看到任务的执行呢?这些任务到底是从哪里开始执行的呢?看到这里,其实已经离谜底不远了。
executor.execute(apply(command,eventExecutor));
这就是整个任务链条的总开关,调用了ThreadPerTaskExecutor.exexute()方法,推倒了整个多米诺骨牌:创建一个新的线程并开始执行。
@Override
public void execute(Runnable command){
threadFactory.newThread(command).start();
}
还记得执行的任务是什么吗?是SingleThreadEventExecutor.this.run(),这个方法会启动NioEventLoop.run()方法,启动死循环来轮询任务,执行任务。
看到这里,还记得我们在做什么吗?我们在跟踪端口的监听流程,还记得我们将register0()封装成了任务放到了任务队列中。从上图我们可以看到,这个循环是在执行任务啊。那我们再回过头来看看register0()这个方法,跟进来看doRegister():实际上就是NIO的channel,注册到了selector上,这里注册的感兴趣的事件类型是0,也就是对啥也不感兴趣,不是应该对OP_ACCEPT感兴趣吗?接着往下看。
doRegister之后,就开始读客户端的事件了。
跟进AbstractNioChannel.doBeginRead()方法:向SelectionKey中添加OP_ACCEPT事件,到此为止,整个Netty对端口的监听流程就完成了。
Netty的线程模型,太过于抽象,任务套任务的执行方式让人看起来就非常的不好理解,作者当时设计这个模型时的思路,真的很让人好奇,是什么样的思维方式,才能想出这种模型呢?这个过程,只有多看几遍,才能一点点的理解,感谢开课吧的老雷老师,讲授的Netty课程让我对Netty的理解能够更加清晰。但是只听别人讲,还是不够的,只有经过自己的仔细推敲,一遍又一遍的打断点,debug,才理解模型的十之一二,还需要看更多遍,才能认识的更清晰。