Netty源码服务器启动流程
看到这篇文章的应该都用过Netty吧。Netty服务端的模板代码如下,我们分析下它是怎么启动的。不要纠结没有关闭连接的代码,毕竟我们只是用这段代码来debug。这篇文章我主要写的是Netty服务端的启动流程。
读完这篇文章你会知道:
- Netty的几大组件的关系是什么?包括NioEventLoopGoup, NioEventLoop, Channle, ChannelHandler, Pipeline
- Netty是怎么注册感兴趣的事件的?
- Netty服务的bossGroup收到连接后,是怎么转派到workerGroup的。
- Netty服务端启动时经历了什么?
package netty.simple;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 28)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MyChatHandler());
}
});
ChannelFuture sync = bootstrap.bind(6666).sync();
sync.channel().closeFuture().sync();
}
}
EventLoopGroup事件循环组
Netty是基于Reactor事件模型的,它将通道的连接和处理分开,bossGroup负责处理NioServerSocketChannel通道连接,通道连接后生产NioSocketChannel分派到workerGroup处理通道的读写事件和业务逻辑。具体怎么通道怎么分配的,接下来再说明。
EventLoopGroup顾名思义,它就是EventLoop组,维护一堆EventLoop的,如下图(NioEventGroup实现了EventExcutor接口)。默认new EventLoopGroup()会生成(cpu核心数*2)个EventLoop,每个EventLoop绑定一个端口,所以如果服务只绑定一个端口的话,就指定为1个接口,即new EventLoopGroup(1);
NioEventLoop事件循环
每个EventLoop都有一个死循环,负责监听Channel上的事件。看NioEventLoop的继承链,它是一个EventExecutor、EventLoop,所以NioEventLoop是一个线程池。NioEventLoop的父类SingleThreadEventExecutor维护了一个线程池对象。
NioEventLoop的execute(Runable r)方法,这是线程次的提交方法,当提交一个线程到NioEventLoop中时,就是执行这个方法,它做的工作如下图,是将提交过来的线程task入队,然后执行NioEventLoop的线程提交方法execute,先将task入队,再看情况执行startThread()。
跟踪进去,它会执行doStartThread()方法。这个方法提交一个线程,立马执行了SingleThreadEventExecutor.this.run()方法。这里,虽然NioEventLoop没有继承Thread或者实现Runnable接口,不能认为它是一个线程实现类。但是它有自己的run()方法,并且初次启动NioEventLoop时会执行SingleThreadEventExecutor.this.run()方法,因此暂且可以认为它是一个线程吧。
那NioEventLoop这个名义上的线程它的run方法是什么呢,如下图,他就是一个死循环了。说到这里,就知道问什么我们说NioEventLoop是一个死循环的线程了吧。
Channel通道
再看到ChannelFuture f = b.bind(PORT).sync();这一句的bind方法,它做了初始化和注册通道的任务。即ServerBootstrap初始化了注册了通道。
他会根据.channel(NioServerSocketChannel.class)这句代码生成一个服务端的NioServerSocketChannel。
Channel初始化时,会给Channel塞很多东西,比如感兴趣的OP_ACCEPT事件,和非阻塞的配置,看到这里就明白了Netty底层还是NIO,Netty其实就是对NIO的封装了。
还向该Channel的pipeline中加入了一个Channel初始化器ChannelInitializer,那么ChannelInitializer的initChannel什么时候执行呢?先剧透一下,这个方法在Channel注册完成后执行,现在还在Channel初始化阶段,还没执行。
Pipeline管道
写过Netty小demo的都知道,我们需要处理自己的业务逻辑时都是向Channel对应的pipeline中加入我们的ChannelHandler。这里就讲一下围绕Channel的组件吧。
如下图,每一个Channel都有自己所属的EventLoop和Pipeline。
pipeline维护了链表形式的ChannelHandlerContext,每次addLast()添加ChannelHandler到pipeline中时,都会被转成ChannelHandlerContext并添加一个链表节点
register注册
Channel初始化完成之后,会执行到注册阶段,如下代码,它提交了一个任务给到EventLoop,上面的分析我们知道,提交EventLoop.execute后,会先将task入队,然后启动死循环执行task,这里是register0方法。
那么register0就是异步通过EventLoop执行的了。main线程继续执行,我们断点在了NioEventLoop的线程,切换过去可以发现它启动了死循环。
调到底层进行注册了。
接下来看到没有,它要执行ChannelInitilizer的initChannel方法了。
它是怎么执行的呢,如下图,调用的是pipeline的execute方法。
跟踪进去,开始执行方法了。
跳到我们最初的initChannel方法。可以看到最后提交了一个task到EventLoop中。往pipeline中添加了一个ServerBootstrapAcceptor。bossGroup是怎么实现将连接建立后分配给workGroup的呢,这就是关键。
SocketChannel连接分配
我们知道,如果有客户端连接到服务端的话,会依次执行pipeline中的ChannelHandler,上面我们可以看到,NioServerSocketChannle最后一个ChannelHandler就是ServerBootstrapAcceptor连接分配器。
我们用客户端连接上来,发现它执行到了ServerBootstrapAcceptor的channelRead方法。并拿到一个SockerChannel,向SocketChannel添加了ChannelInitializer。然后向childGroup中注册该SocketChannel。
ChildGroup会选择一个EventLoop来注册这个SocketChannel。
至此,Netty服务端启动流程结束。