美文网首页
Netty源码分析-Server端启动

Netty源码分析-Server端启动

作者: yunkai_zhang | 来源:发表于2017-08-10 10:34 被阅读0次

    本文主要对Netty中Server端启动进行分析,分析Server端是如何绑定端口,初始化Selector,启动NioEventLoop,并最终实现Reactor模式的。
    首先看一段经典的Server启动代码。可以看到该过程中实例化了两个EventLoopGroup县城组:bossGroup、workerGroup。可以看到这个Server采用的是Reactor的多线程模式。创建的SimpleNettyServerHandler是通过childHandler()方法加入到workerGroup线程去执行的。

        public void bind(int port) throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new SimpleNettyServerHandler());
                        }
                    });
                ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
                channelFuture.channel().closeFuture().sync();
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    

    1. bind()入口

    下面就以serverBootstrap.bind(port)为入口,进一步分析实际Server的启动流程的,直接进入到doBind(final SocketAddress localAddress)方法:
    1.会调用initAndRegister()初始化并注册一个Channel,此处的Channel是一个NioServerSocketChannel,该方法返回一个ChannelFuture。
    2.判断ChannelFuture是否已经执行完成,如果执行完成会调用doBind0()方法;如果没有执行完成,也会通过添加监听器等待执行完成后再调用doBind0()方法。
    因此,重点就落在initAndRegister()doBind0()这两个方法上来了。

        private ChannelFuture doBind(final SocketAddress localAddress) {
            final ChannelFuture regFuture = initAndRegister();
            final Channel channel = regFuture.channel();
            if (regFuture.cause() != null) {
                return regFuture;
            }
    
            if (regFuture.isDone()) {
                // At this point we know that the registration was complete and successful.
                ChannelPromise promise = channel.newPromise();
                doBind0(regFuture, channel, localAddress, promise);
                return promise;
            } else {
                // Registration future is almost always fulfilled already, but just in case it's not.
                final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
                regFuture.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        Throwable cause = future.cause();
                        if (cause != null) {
                            promise.setFailure(cause);
                        } else {
                            promise.registered();
    
                            doBind0(regFuture, channel, localAddress, promise);
                        }
                    }
                });
                return promise;
            }
        }
    

    1.1 initAndRegister()分析

    分析一下initAndRegister()方法。通过函数名也大致可以看出该方法工作分为两部分:init(Channel channel)register

        final ChannelFuture initAndRegister() {
            Channel channel = null;
            try {
                channel = channelFactory.newChannel();
                init(channel);
            } catch (Throwable t) {
                if (channel != null) {
                    // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                    channel.unsafe().closeForcibly();
                }
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
    
            ChannelFuture regFuture = config().group().register(channel);
            if (regFuture.cause() != null) {
                if (channel.isRegistered()) {
                    channel.close();
                } else {
                    channel.unsafe().closeForcibly();
                }
            }
    
            // If we are here and the promise is not failed, it's one of the following cases:
            // 1) If we attempted registration from the event loop, the registration has been completed at this point.
            //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
            // 2) If we attempted registration from the other thread, the registration request has been successfully
            //    added to the event loop's task queue for later execution.
            //    i.e. It's safe to attempt bind() or connect() now:
            //         because bind() or connect() will be executed *after* the scheduled registration task is executed
            //         because register(), bind(), and connect() are all bound to the same thread.
    
            return regFuture;
        }
    

    1.1.1 init过程分析

    看一下init(Channel channel)的实现。首先对Channel设置了ChannelOption和AttributeKey,然后会执行最重要的一步:在Channel的pipeline中添加ChannelHandler。具体ChannelPipeline的讲解会在单独的文章,可以看到该ChannelHandler的最终实现是一个ServerBootstrapAcceptor,通过名字就可以看出这是一个专门做Acceptor的handler,具体对它的分析可以放在文章的后边。值得注意的是给pipeline添加ServerBootstrapAcceptor的过程是通过ch.eventLoop().execute(new Runnable()执行的,ch.eventLoop()返回实际就是NioEventLoop(具体可见:),所以实际是通过NioEventLoop中执行非I/O任务执行的。

        @Override
        void init(Channel channel) throws Exception {
            final Map<ChannelOption<?>, Object> options = options0();
            synchronized (options) {
                setChannelOptions(channel, options, logger);
            }
    
            final Map<AttributeKey<?>, Object> attrs = attrs0();
            synchronized (attrs) {
                for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                    @SuppressWarnings("unchecked")
                    AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                    channel.attr(key).set(e.getValue());
                }
            }
    
            ChannelPipeline p = channel.pipeline();
    
            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(childOptions.size()));
            }
            synchronized (childAttrs) {
                currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
            }
    
            p.addLast(new ChannelInitializer<Channel>() {
                @Override
                public void initChannel(final Channel ch) throws Exception {
                    final ChannelPipeline pipeline = ch.pipeline();
                    ChannelHandler handler = config.handler();
                    if (handler != null) {
                        pipeline.addLast(handler);
                    }
    
                    ch.eventLoop().execute(new Runnable() {
                        @Override
                        public void run() {
                            pipeline.addLast(new ServerBootstrapAcceptor(
                                    ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                        }
                    });
                }
            });
        }
    

    1.1.2 register分析

    通过分析上边的init方法,当前的阶段是Channel已经初始化好了,Channel对应的Pipeline也已经初始化完成。但是这个Channel与Reactor线程之间还没有建立联系,下边的register的过程就是将这个Channel在NioEventLoop线程上进行注册。
    注册的核心代码在AbstractChannel$AbstractUnsafe.register方法中,下边的这段堆栈信息展示了是如何调到这个方法的:

    "main@1" prio=5 tid=0x1 nid=NA runnable
      java.lang.Thread.State: RUNNABLE
          at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:456)
          at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:80)
          at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:74)
          at io.netty.channel.MultithreadEventLoopGroup.register(MultithreadEventLoopGroup.java:86)
          at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:332)
          at io.netty.bootstrap.AbstractBootstrap.doBind(AbstractBootstrap.java:283)
          at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:279)
          at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:254)
          at com.zhangyk.server.SimpleNettyServer.bind(SimpleNettyServer.java:35)
          at com.zhangyk.server.SimpleNettyServer.main(SimpleNettyServer.java:50)
    

    register方法的实现非常简单,会将最终的实际执行封装在方法register0(ChannelPromise promise)中,然后根据线程状态决定是在当前线程执行还是放在ch.eventLoop().execute(new Runnable()执行。
    1)执行doRegister(),这是最核心的一步,具体的实现在类io.netty.channel.nio. AbstractNioChannel中,后边有贴源码实现,代码中可以看到会将javaChannel注册到NioEventLoop的selector上,这样NioEventLoop的selector就可以监听到Channel的I/O事件变化。
    2)执行Channel对应Pipeline的fireChannelRegistered()方法
    3)根据是否是firstRegister以及isAutoRead()决定是否执行Pipeline的fireChannelActive()方法和beginRead()方法

            private void register0(ChannelPromise promise) {
                try {
                    // check if the channel is still open as it could be closed in the mean time when the register
                    // call was outside of the eventLoop
                    if (!promise.setUncancellable() || !ensureOpen(promise)) {
                        return;
                    }
                    boolean firstRegistration = neverRegistered;
                    doRegister();
                    neverRegistered = false;
                    registered = true;
    
                    // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                    // user may already fire events through the pipeline in the ChannelFutureListener.
                    pipeline.invokeHandlerAddedIfNeeded();
    
                    safeSetSuccess(promise);
                    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) {
                            pipeline.fireChannelActive();
                        } else if (config().isAutoRead()) {
                            // This channel was registered before and autoRead() is set. This means we need to begin read
                            // again so that we process inbound data.
                            //
                            // See https://github.com/netty/netty/issues/4805
                            beginRead();
                        }
                    }
                } catch (Throwable t) {
                  .......
                }
            }
    
    public abstract class AbstractNioChannel extends AbstractChannel {
        protected void doRegister() throws Exception {
            boolean selected = false;
            for (;;) {
                try {
                    selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                    return;
                } catch (CancelledKeyException e) {
                    if (!selected) {
                        // Force the Selector to select now as the "canceled" SelectionKey may still be
                        // cached and not removed because no Select.select(..) operation was called yet.
                        eventLoop().selectNow();
                        selected = true;
                    } else {
                        // We forced a select operation on the selector before but the SelectionKey is still cached
                        // for whatever reason. JDK bug ?
                        throw e;
                    }
                }
            }
        }
    }
    

    至此,可以看到,我们已经创建并初始化了NioServerSocketChannel,并且将该Channel注册到了NioEventLoop的Selector上。实际上,NioEventLoop的Loop循环已经可以开始监听ServerChannel的I/O事件了。

    1.2 dobind分析

    由上所知,initAndRegister()之后,Channel已经初始化好,并且对应的Selector都已经注册监听,NioEventLoop也已经开始Loop循环。现在还需要将该channel与我们所要监听的port端口进行绑定。可以看到,端口绑定的操作也是通过channel.eventLoop().execute 执行的。

        private static void doBind0(
                final ChannelFuture regFuture, final Channel channel,
                final SocketAddress localAddress, final ChannelPromise promise) {
    
            // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
            // the pipeline in its channelRegistered() implementation.
            channel.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    if (regFuture.isSuccess()) {
                        channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                    } else {
                        promise.setFailure(regFuture.cause());
                    }
                }
            });
        }
    

    2. 启动状态图

    此处贴用别人blog的一张图来表示一下。启动的操作一部分由Main线程完成,另一部分由bossGroup线程(即NioEventLoop线程)完成。
    NioEventLoop线程除了执行I/O任务(第二个),还执行了doRegister()、doBind()、finishPipeline()这三个非I/O任务。

    server启动状态图

    3. ServerBootstrapAcceptor类分析

    通过上边的分析,我们知道ServerBootstrapAcceptor是Acceptor Pipeline中的一个handler。由它负责对NioServerChannel上发生I/O事件进行处理。譬如,当NioServerChannel上有一个新的连接请求时,会由bossGroup线程的I/O任务Selector监听到(具体可参见NioEventLoop中I/O任务的分析过程),并通过调用 processSelectedKeys()方法,并最终执行到ServerBootstrapAcceptorchannelRead(ChannelHandlerContext ctx, Object msg)方法。
    分析一下channelRead(ChannelHandlerContext ctx, Object msg)方法,参数里边msg实际会是通过accept与客户端建立的一个子连接child。具体的实行过程:
    1.给这个子连接child的pipeline 增加Handler,这个Handler就是文章最开始那段代码中通过childHandler添加的那个Handle。代码及child.pipeline().addLast(childHandler)
    2.给子连接设定一些属性childOptions、AttributeKey等
    3.将子连接child注册到childGroup中.后续关于该子连接的一些I/O事件及其执行会在childGroup的EventLoop中。

    private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
    
            private final EventLoopGroup childGroup;
            private final ChannelHandler childHandler;
            private final Entry<ChannelOption<?>, Object>[] childOptions;
            private final Entry<AttributeKey<?>, Object>[] childAttrs;
            private final Runnable enableAutoReadTask;
    
            ServerBootstrapAcceptor(
                    final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
                    Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
                this.childGroup = childGroup;
                this.childHandler = childHandler;
                this.childOptions = childOptions;
                this.childAttrs = childAttrs;
    
                // Task which is scheduled to re-enable auto-read.
                // It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
                // not be able to load the class because of the file limit it already reached.
                //
                // See https://github.com/netty/netty/issues/1328
                enableAutoReadTask = new Runnable() {
                    @Override
                    public void run() {
                        channel.config().setAutoRead(true);
                    }
                };
            }
    
            @Override
            @SuppressWarnings("unchecked")
            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                final Channel child = (Channel) msg;
    
                child.pipeline().addLast(childHandler);
    
                setChannelOptions(child, childOptions, logger);
    
                for (Entry<AttributeKey<?>, Object> e: childAttrs) {
                    child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
                }
    
                try {
                    childGroup.register(child).addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (!future.isSuccess()) {
                                forceClose(child, future.cause());
                            }
                        }
                    });
                } catch (Throwable t) {
                    forceClose(child, t);
                }
            }
    
            private static void forceClose(Channel child, Throwable t) {
                child.unsafe().closeForcibly();
                logger.warn("Failed to register an accepted channel: {}", child, t);
            }
    
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                ......
        }
    

    相关文章

      网友评论

          本文标题:Netty源码分析-Server端启动

          本文链接:https://www.haomeiwen.com/subject/lavcrxtx.html