本文主要记录下我在学习Netty源代码过程中,以org.jboss.netty.example.echo.EchoServer为例,在启动Server端的时候,Netty是如何建立Boss与NIOWorker的,以及如何建立Selector、绑定服务器端口等过程的细节,借此来学习Netty的Pipeline、ChannelHandle、Boss、NioWorker等各自的分工是怎样,它们又是如何协作的?
(基础一)Netty网络模型图
(基础二)ChannelPipeline原理
具体的Netty实现原理上可以参见《Netty实现原理浅析》,该文写得很好,我这里不再详细记述。
以下为我在代码阅读的过程记述的一些要点,先就有这些东西,后续再补充完整。
第一节:建立MainReactor和SubReactor线程池
ServerBootstrap bootstrap = new ServerBootstrap( new NioServerSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));指定创建一个NioServerSocketChannelFactory(),建立一个BossExecutor的执行器,和一个workExecutor的执行器。
bootstrap.setPipelineFactory(new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { return Channels.pipeline(new EchoServerHandler()); } });
设置服务器端的ChannelPipelineFactory对象,在该工厂对象中指定使用哪一个ChannelPipeline的实现实例,在EchoServer中使用DefaultChannelPipeline来作为ChannelPipeline,同时指定ChannelPipeline中需要使用的ChannelHandle链接。 在指定ChannelPipeline的ChannelHandle的过程,对于每一个DefaultChannelPipeline的实例对象,都建立一个<name,ChannelHandle>的映射关系, 并且对于每一个ChannelHandle都关联一个DefaultChannelHandleContext对象,在该对象中指定了当前ChannelHandle的前一个、后一个ChannelHandle, ChannelPipeline中的DefaultChannelHandleContext是按照ChannelHandle1(Head:prev=null,next=ChannelHandle2)->ChannelHandle1(tail:prev=ChannelHandle1,next=null)构造ChannelHandle链的,对于downstream的事件,是从tail->head的顺序执行ChannelHandle的,所以prev为空表示downstream链的末端,next为空表示upstream链的末端,在downstream链的末端则需要执行NioServerSocketPipelineSink来处理了。
第三节:建立一个Server端启动所对应的ChannelHandle链
bootstrap.bind(new InetSocketAddress(8080));
关键的动作尽在此步骤中了。首先建立了队列new LinkedBlockingQueue<ChannelFuture>(),再次之后就建立了一个处理Boss的ChannelHandle实例对象Binder(), 之后再建立了一个DefaultChannelPipeline对象来作为Boss的ChannelPipeline,该ChannelPipeline中的ChannelHandle对象就只有一个Binder()的ChannelHandle了,并且该Binder对象实例只是一个处理upstream的ChannelHandle
第四节:打开服务器端Socket,发起一个Channel的Open的Upstream事件,后续将OPEN事件转换为一个Downstream的BIND事件
Channel channel = getFactory().newChannel(bossPipeline);
利用上述第(1)步中建立的对象NioServerSocketChannelFactory来建立一个ServerSocketChannel,在创建NioServerSocketChannel对象的时候, 会调用如下代码块:
socket = ServerSocketChannel.open();
socket.configureBlocking(false);
紧接下来就是调用fireChannelOpen(this)来调用步骤(3)中建立的DefaultChannelPipeline对象,发送一个upstream事件,ChannelState为OPEN 这个是触发upstream的ChannelHandle的第一个入口,后续就会依次调用DefaultChannelHandlerContext的Next所指向的ChannelHandle的handleUpstream()方法, 对于EchoServerHandle来说,对应的handleUpstream()方法就会调用SimpleChannelsUpstreamHandle的handleUpstream()方法,对于ChannelStateEvent.OPEN事件来说, 会调用ServerBootstrap.Binder.channelOpen()方法,在Binder这个ChannelHandle处理完成之后,又会使用DefaultChannelHandleContext.sendUpstream(evt)来处理后续的链节点, 只是这里只有一个ChannelHandle,到这里就执行完毕了。 在ServerBootstrap.Binder.channelOpen()方法中有如下代码:boolean finished = futureQueue.offer(evt.getChannel().bind(localAddress));将一个ChannelState.OPEN事件转换为 一个BIND事件了,即调用NioServerSocketChannel.bind()方法,具体的实现代码在AbstractChannel.bind()方法中,实则是调用Channels.bind()方法,其中Channels类是一个工具类。
第五节:完成DownStream的BIND事件的处理
ChannelFuture future = future(channel); channel.getPipeline().sendDownstream(new DownstreamChannelStateEvent( channel, future, ChannelState.BOUND, localAddress));
Channels.bind()方法中主要是如上代码,将ChannelState.BIND事件,构造为一个downstream事件,由Boss的ChannelPipeline中的ChannelHandle链来完成这个downstream的处理。 DefaultChannelPipeline.sendDownstream()方法会从ChannelHandle链的链尾节点开始找能够处理downstream的ChannelHandle节点来处理该事件,在这里由于ServerBootstrap.Binder只是一个 Upstream的Handle,所以没有对应的downstream的ChannelHandle节点可以处理该事件,直接就由(1)步所建立的NioServerSocketPipelineSink对象来完成downstream的末端处理,即会调用NioServerSocketPipelineSink.eventSunk()方法,再到NioServerSocketPipelineSink.handleServerSocket()方法,再又调用了NioServerSocketPipelineSink.bind()方法。
第六节:将服务器端Socket绑定到指定端口
channel.socket.socket().bind(localAddress, channel.getConfig().getBacklog());
NioServerSocketPipelineSink.bind()方法中有如上代码,完成关键的一步,即将建立了一个ServerSocketChannel,并且绑定到指定的端口
第七节:触发一个Upstream的BOUND事件
fireChannelBound(channel, channel.getLocalAddress());
NioServerSocketPipelineSink.bind()方法中有如上代码,触发一个Upstream的BOUND事件,但是遗憾的是后续并没有看到什么有特别用途的操作发生。
第八节:将服务器端Socket与selector绑定起来,完成对后续Client链接的监听
Executor bossExecutor = ((NioServerSocketChannelFactory) channel.getFactory()).bossExecutor; DeadLockProofWorker.start( bossExecutor, new ThreadRenamingRunnable( new Boss(channel), "New I/O server boss #" + id + " (" + channel + ')')); bossStarted = true;
这个是NioServerSocketPipelineSink.bind()方法中的一段代码,其中有一个new Boss(channel)操作,会执行一个Selector.open()动作,再继而以channel.socket.register(selector, SelectionKey.OP_ACCEPT);这个动作把刚才新建的NioServerSocketChannel与一个Selector的实例绑定起来。其中的DeadLockProofWorker.start()这个是将一个绑定的Boss线程放到(1)步骤中的bossExecutor线程池中,开始执行Boss线程。
第九节:BOSS线程轮询接收Client来链接请求
for (;;) { try { if (selector.select(1000) > 0) { selector.selectedKeys().clear(); } SocketChannel acceptedSocket = channel.socket.accept(); if (acceptedSocket != null) { registerAcceptedChannel(acceptedSocket, currentThread); } } catch (SocketTimeoutException e) { // Thrown every second to get ClosedChannelException // raised. } catch (CancelledKeyException e) { // Raised by accept() when the server socket was closed. } catch (ClosedSelectorException e) { // Raised by accept() when the server socket was closed. } catch (ClosedChannelException e) { // Closed as requested. break; } catch (Throwable e) { logger.warn( "Failed to accept a connection.", e); try { Thread.sleep(1000); } catch (InterruptedException e1) { // Ignore } } }
以上代码为NioServerSocketPipelineSink.Boss.run()中的一部分,主要母的就是让selector来不断的轮询,看是否有客户端的链接过来,有的话就利用registerAcceptedChannel(acceptedSocket, currentThread);来将链接来的Socket注册到NioWorker,有NioWorker(subreactor)来完成对客户端链接的处理。
至此Netty的服务端启动完成了。
对于服务端的启动过程,Netty也是以ChannelHandler链的方式来实现的,先是实现一个Upstream的OPEN事件,再变换为Downstream的BOUND事件,不知为什么原因又触发了一个第七节的Upstream的BOUND事件出来,但是该事件并没有完成什么有效的操作。这个要存疑,待后续再阅读与品味!