当前位置: 首页 > 后端技术 > Java

[Netty]6.服务器启动流程-源码解读

时间:2023-04-01 21:49:46 Java

1.前言在实际开发过程中,通过Netty提供的高度封装的Api,我们可以很方便的构建自己的服务器程序,如下例publicstaticvoidmain(String[]args)throwsException{//实例化bossGroup和workerGroup//bossGroup传入参数1,表示只包含一个EventLoop//workerGroup使用无参构造函数,默认实例化(CPU核数*2)EventLoopEventLoopGroupbossGroup=newNioEventLoopGroup(1);EventLoopGroupworkerGroup=newNioEventLoopGroup();尝试{ServerBootstrapb=newServerBootstrap();b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)//指定Channel的类型,本例为服务端,所以使用NioServerSocketChannel.option(ChannelOption.SO_BACKLOG,100)//设置NioServerSocketChannel的option选项。handler(newLoggingHandler(LogLevel.INFO))//在NioServerSocketChannel对应的ChannelPipeline管道上设置ChannelHandler.childHandler(newChannelInitializer(){//在NioSocketChannel对应的ChannelPipeline管道上设置ChannelHandler@OverridepublicvoidinitChannel(SocketChannelch)throwsException{ChannelPipelinep=ch.pipeline();p.addLast(newEchoServerHandler());//添加EchoServerHandler处理器}});//启动服务器ChannelFuturef=b.bind(PORT).sync();//阻塞直到NioServerSocketChannel关闭f.channel().closeFuture().sync();}finally{//关闭所有EventLoop以终止所有线程bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();这里的bossGroup只包含一个NioEventLoop,主要用来处理ACCEPT事件。默认实例化WorkerGroup(CPU核数*2)上面的NioEventLoop是源码中EchoServer的一个简单例子,作用是接收客户端发来的消息,并写回给客户端。用这个例子来解读服务器的启动过程。2、服务器的启动过程。服务器的启动主要和两个线程有??关,分别是主线程和bossGroup的NioEventLoop对应的线程。假设线程名称为NioEventLoopGroup2-1。我们来看看这两个线程的执行流程。2.1主线程的执行流程首先我们看到在main方法中,服务器是通过ServerBootstrap的bind()方法启动的,所以这里是入口。下面是主线程的执行流程图。可以看到,main方法主要做了两件事:initAndRegister:初始化NioServerSocketChannel,注册到bossGroup的NioEventLoop(因为bossGroup这里只有一个EventLoop,所以是固定的),ReturnChannelFuture(因为它为异步注册)addListener:为异步注册返回的ChannelFuture对象添加监听器。注册成功后会通知监听器,监听器内部会执行doBind0方法将[绑定端口]任务提交给NioEventLoop,下面我们来看看他们的具体步骤1)initAndRegister可以看到有其中主要三个步骤:通过ReflectiveChannelFactory类的newChannel()方法实例化NioServerSocketChannel(对ServerSocketChannel的封装),调用init方法初始化NioServerSocketChannel,设置其选项和属性,调用ChannelPipeline类的addLast方法,添加ChannelInitializer其ChannelPipeline管道将任务【NioServerSocketChannel注册】提交给bossGroup的NioEventLoop2)addListener在上一步initAndRegister执行完后会返回一个名为regFuture的ChannelFuture对象,如果regFuture已经完成,直接执行doBind0方法,否则添加一个监听器,当regFuture完成后,会通知它执行doBind0(该方法内部将[绑定端口]任务提交给NioEventLoop),如下,主线程启动流程结束,我们来看看bossGroup的NioEventLoopGroup2-1线程呢?2.2[bossGroup]NioEventLoopGroup2-1线程的执行过程上述主线程执行过程中,向bossGroup的NioEventLoop提交了一个[NioServerSocketChannel注册]任务,NioEventLoop接收启动第一个任务,开始初始化工作,启动并绑定线程,执行NioEventLoop类的run方法。run方法的步骤如下:上一篇文章提到过,NioEventLoop本身就是一个无限循环不断处理接收到的Tasks,所以这里也是一样,开始执行NioEventLoop的run方法,内部执行runAllTask??s执行所有任务的方法(注:任务执行时也可以将任务提交给NioEventLoop)接下来我们看runAllTask??s方法从执行流程图可以看出,它的主要内容是执行4个任务,即执行task[NioServerSocketChannelregistration]executethetask[AddServerBootstrapAcceptortoChannelPipeline]executethetask[bindport]executethetask[activateNioServerSocketChannel]那么你可能知道我有疑惑,为什么主线程提交的任务只有一个,但是有四个任务在这里执行?答案是在任务执行的过程中,向NioEventLoop提交了一个新的任务先来看执行第一个任务1)执行任务【NioServerSocketChannel注册】执行任务【NioServerSocketChannel注册】,主要有4步调用AbstractNioChannel的doRegister方法,在Channel之前注册javanio的ServerSocketChannelNioEventLoop的Selector在主线程执行的过程中,在ChannelPipeline中添加ChannelInitializer,这里调用ChannelInitializer的handlerAdded方法,将配置好的LoggingHandler添加到ChannelPipeline中,将自己从ChannelPipeline中移除,提交任务[ChannelPipelineaddsServerBootstrapAcceptorinboundprocessor]所以当前ChannelPipeline中的顺序是:HeadContext->LoggingHandler->TailContext设置任务执行成功,因为在主线程中添加了监听器到regFuture,所以这里会通知监听器,任务会在监听器内部提交[绑定端口]最后一步是调用当前ChannelPipeline的fireChannelRegistered方法,从ChannelPipeline的头部开始遍历,依次找到重写了channelRegistered方法的ChannelHandler。如果在channelRegistered方法中调用了ctx.fireChannelRegistered(),继续遍历到这里,执行第一个任务【NioServerSocketChannel注册】。2)执行任务[AddServerBootstrapAcceptortoChannelPipeline]第二个任务是向ChannelPipeline添加一个ServerBootstrapAcceptor处理器。这个处理器的作用是接收新的连接,注册到workerGroup的NioEventLoop上,后面会用到。3)执行任务【绑定端口】第三个任务是绑定端口。它会先从ChannelPipeline的尾部开始遍历,然后去寻找重写了bind方法的ChannelHandler。如果在bind方法中调用ctx.bind(),则继续向前遍历。最后调用HeadContext的bind方法,执行ServerSocketChannel.bind()绑定端口,提交任务【激活NioServerSocketChannel】。4)执行任务[ActivateNioServerSocketChannel]激活NioServerSocketChannel的最终目的是设置SelectionKey的监听事件,NioServerSocketChannel是一个ACCEPT事件。具体步骤是先调用ChannelPipeline的fireChannelActive方法,从ChannelPipeline头部开始遍历,依次调用ChannelHandler的channelActive方法。在HeadContext的channelActive方法中,执行了readIfIsAutoRead方法,调用了ChannelPipeline的read方法,从tail开始遍历,依次调用ChannelHandler的read方法。在HeadContext的read方法中,底层通过SelectionKey.interestOps设置了NioServerSocketChannel的监听事件。runAllTask??s方法执行完后,NioEventLoop的run方法回到循环体头部,计算完策略后,调用Selector的select()方法阻塞当前线程(当有任务添加时,Selector的wakeUp方法会用于主动唤醒Selector)2.3接收新连接并提交注册。上面说到bossGroup的NioEventLoop调用Selector的select()方法监听ACCEPT事件,阻塞当前线程。稍后,当有新的连接进来时,select()被唤醒,开始注册新的连接。大体流程如上图所示。调用NioMessageUnsafe的read方法,内部调用NioServerSocketChannel的doReadMessages接收连接,并将SocketChannel封装为NioSocketChannel。源码如下,然后调用DefaultChannelPipeline的fireChannelRead方法,从ChannelPipeline的头部开始,一个一个调用ChannelHandler的channelRead方法。可以看到这里调用了ServerBootstrapAcceptor的channelRead方法(ServerBootstrapAcceptor是之前执行第二个任务时添加到ChannelPipeline中的),而[NioSocketChannel注册]任务内部提交到workerGroup的NioEventLoop。如下提交[NioSocketChannelRegistration]任务后,bossGroup的NioEventLoop继续调用Selector的select方法进行阻塞。至此,接收连接的过程就结束了。3、小结至此,服务器的启动过程就告一段落了。后面会继续讲解NioSocketChannel注册到workerGroup的NioEventLoop的过程,以及NioSocketChannel收到客户端发送的数据时的读取过程。