Netty源碼之服務端啓動與綁定

通常我們用ServerBootstrap引導服務器,

public final class SimpleServer {

  public static void main(String[] args) throws Exception {
      EventLoopGroup bossGroup = new NioEventLoopGroup(1);
      EventLoopGroup workerGroup = new NioEventLoopGroup();

      try {
          ServerBootstrap b = new ServerBootstrap();
          b.group(bossGroup, workerGroup)
                  .channel(NioServerSocketChannel.class)
                  .handler(new SimpleServerHandler())
                  .childHandler(new ChannelInitializer<SocketChannel>() {
                      @Override
                      public void initChannel(SocketChannel ch) throws Exception {
                      }
                  });

          ChannelFuture f = b.bind(8888).sync();

          f.channel().closeFuture().sync();
      } finally {
          bossGroup.shutdownGracefully();
          workerGroup.shutdownGracefully();
      }
  }
}

其中group()無非傳入兩個EventLoopGroup,一個負責接收連接,一個負責處理連接上的響應。這之後的邏輯基本上在EventLoop跟ChannelPipeLine中理得差不多了。

這次我們重點來看ChannelFuture f = b.bind().sync(); bind()方法使netty開始對本地端口的綁定與監聽。

bind邏輯的實現在其超類AbstractBootstrap中

    public ChannelFuture bind() {
       //檢查group和channelFactory屬性是否爲null
        validate();
        SocketAddress localAddress = this.localAddress;
        if (localAddress == null) {
            throw new IllegalStateException("localAddress not set");
        }
        return doBind(localAddress);
    }

其中validate()方法是對netty中的group跟channelFactory進行非null驗證,保證都是已經配置完成的(gorup是在.goup()中綁定的,channelFactory是在第一次.channel時候,生成靜態BootstrapChannelFactory配置進去)。然後判斷本地地址是否已經綁定,最後調用doBind()繼續後面的綁定。

private ChannelFuture doBind(final SocketAddress localAddress) {

        //1.初始化channel並將初始化好的channel註冊到事件循環
        final ChannelFuture regFuture = initAndRegister();

        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        final ChannelPromise promise;

        /**
         * 2.判斷初始化且註冊任務是否已經完成,如果已經完成,則調用bind端口方法
         * 注意這裏regFuture.isDone()方法,返回ture,只是標示任務是否已經完成,完成的情況可能有:
         * 1.正常終止、2.異常、3.取消
         */
        if (regFuture.isDone()) {
            promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            promise = new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    doBind0(regFuture, channel, localAddress, promise);
                }
            });
        }

        return promise;
    }

其中有涉及到channelFutrue的內容,會在後面的博文中詳細介紹。

首先調用initAndRegister(),將初始化好的Channel註冊到EventLoopGroup中。

    final ChannelFuture initAndRegister() {

        final Channel channel = channelFactory().newChannel();

        try {
            //初始化Channel實例:對channel實例進行相關參數設置。這裏調用的是對應子類的實現
            init(channel);
        } catch (Throwable t) {
            channel.unsafe().closeForcibly();
            return channel.newFailedFuture(t);
        }

        ChannelFuture regFuture = group().register(channel);

        //如果註冊失敗,regFuture.cause會返回失敗的異常對象
        //如果註冊出現異常,並channel已經註冊,那麼關閉。如果沒有註冊,那麼強行關閉

        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

先通過反射得到channel實例,再調用具體子類的init()方法,對channel實例進行相關參數設置。

    @Override
    void init(Channel channel) throws Exception {

        //將傳入Bootstrap
        final Map<ChannelOption<?>, Object> options = options();
        synchronized (options) {
            channel.config().setOptions(options);
        }

        final Map<AttributeKey<?>, Object> attrs = attrs();

        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e : attrs.entrySet()) {
                @SuppressWarnings("unchecked")
                AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        //將AbstractBootstrap的Hanlder添加到NioServerketChannel
        //就是把handler(new SimpleServerHandler())這個handler添加到NioServerketChannel的pipline
        
        ChannelPipeline p = channel.pipeline();


        if (handler() != null) {
            p.addLast(handler());
        }

        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()));
        }

        /**
         * ChannelInitializer是一個特殊的ChannelInboundHandler,當channelRegistered事件觸發後,
         * 會調用initChannel方法,調完後,這個handler會從piplne中刪除
         */
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new ServerBootstrapAcceptor(
                        currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });
    }

首先在服務端通過加鎖的方式配置options跟atts屬性,取出NioServerChannel對應的pipline,將之前配置的handler添加到pipline中。

最後通過添加ChannelInitializer(類似於佔位符)的方式,向pipline中添加ServerBootstrapAcceptor實例。

我們先看看ChannelInitializer的巧妙實現方式,以下是它的channelRegistered方法,也就是在之後通道註冊之後,會鏈式調用pipline中的相應方法,也就會調用以下方法。

 @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ChannelPipeline pipeline = ctx.pipeline();
        boolean success = false;
        try {
            //調用initChannel方法進行ChannelPipline初始化
            initChannel((C) ctx.channel());
            //從pipline刪除這個handler
            pipeline.remove(this);

            //鏈式觸發fireChannelRegistered事件
            ctx.fireChannelRegistered();
            success = true;
        } catch (Throwable t) {
            logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), t);
        } finally {
            if (pipeline.context(this) != null) {
                pipeline.remove(this);
            }
            if (!success) {
                ctx.close();
            }
        }
    }

無非先initChannel()這是我們剛剛在ServerBootstrap.init()中實現的方法,再之後將ChannelInitializer這個handler從鏈中刪除,繼續調用後面的相應的事件。(其實通過這個類似於佔位符的方式我們可以在引導過程中添加多個channelhandler,無非在initChannel()的實現中完成)。

 p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new ServerBootstrapAcceptor(
                        currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });

我們再回到ServerBootstrapAcceptor,ServerBootstrapAcceptor重寫了channelRead(ctx,msg)方法,主要的作用是當有client連接上Server的時,pipline觸發channelRead 事件,然後會給pipline添加用戶自己的handler。其中第二個形參msg 在Nio中,是Server accpet客戶端的connect連接後,產生的NioSocketChannel對象。

init()方法完成通道的配置之後,我們繼續initAndRegister的之後步驟ChannelFuture regFuture = group().register(channel);通道完成了後,無非把通道綁定到EventLoopGroup上,(這裏是group,而非childgroup,也就是最上文的bossGroup)。


調用了NioEventGroup的register(),具體的邏輯在其超類MultithreadEventLoopGroup中。

 @Override
    public EventLoop next() {
        return (EventLoop) super.next();
    }

    @Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }

    @Override
    public ChannelFuture register(Channel channel, ChannelPromise promise) {
        return next().register(channel, promise);
    }

無非調用next().register(),我們先來看super.next()的實現

    public EventExecutor next() {
        return this.children[Math.abs(this.childIndex.getAndIncrement() % this.children.length)];
    }

由於EventLoopGroup在初始化時候創建了singleThreadEventLoop數組,其實就是NioEventLoop(詳細見之前的博客EventLoop解析),所以通過如上方式選擇相應的NioEventLoop返回,並調用其register(),childIndex是一個AtomicInteger類。

由於NioEventLoop extends SingleThreadEventLoop,NioEventLoop沒有重寫該方法,因此看 SingleThreadEventLoop類中的register方法。

    @Override
    public ChannelFuture register(Channel channel) {
        return register(channel, new DefaultChannelPromise(channel, this));
    }

    @Override
    public ChannelFuture register(final Channel channel, final ChannelPromise promise) {
        if (channel == null) {
            throw new NullPointerException("channel");
        }
        if (promise == null) {
            throw new NullPointerException("promise");
        }

        channel.unsafe().register(this, promise);
        return promise;
    }
具體調用了usafe類的register()方法,跟蹤代碼,具體邏輯的主要實現在AbstractNioUnsafe的doRegister()方法。
 @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (; ; ) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
                /**
                 * CancelledKeyException拋出的場景:
                 * 當前Channel已經註冊了給定的selector,但是相應個key卻已經被取消了
                 */
                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;
                }
            }
        }
    }

selectionKey = javaChannel().register(eventLoop().selector, 0, this);就完成了ServerSocketChannel註冊到Selector中。

到這裏基本上把initAndRegister介紹完了。

確保註冊與初始化都已經完成,調用doBind0();(AbstractBootstrap.doBind0())

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.

           //判斷註冊事件是否成功,如果註冊成功,則把bind任務丟到任務隊列
           channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                //channel註冊到eventloop成功後,纔開始調用Channel的bind方法
                if (regFuture.isSuccess()) {
                    //調用Channel的bind方法
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

因爲之前initAndRegister已經完成,所以eventloop線程模型已經建立(channel已經註冊至eventLoop中),這裏只需將bind跟添加監聽器任務扔至Task隊列中,異步地完成。

channel的bind函數調用了pipline的bind調用了tail的bind,最終調用了跟tailhandler綁定DefaultChannelHandlerContext的bind函數

 @Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        validatePromise(promise, false);

        //從tail ---> head找Outbound執行
        final DefaultChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }

        return promise;
    }

其中findContextOutBound()函數無非找到下一個類型爲outbound的handler

    private DefaultChannelHandlerContext findContextOutbound() {
        DefaultChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }
這裏鏈式調用每一個outboundhandler的bind函數,最後到head的headhandler中bind實現
        @Override
        public void bind(
                ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
                throws Exception {
            unsafe.bind(localAddress, promise);
        }

具體邏輯在AbstractUnsafe.bind()

 @Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            if (!ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() &&
                    Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                    localAddress instanceof InetSocketAddress &&
                    !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                                "is not bound to a wildcard address; binding to a non-wildcard " +
                                "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
                //調用子類的doBind()方法,如果使用的NIO那麼這裏調用的就是NioServerSocketChannel實現的doBind()方法
                doBind(localAddress);
            } catch (Throwable t) {
                promise.setFailure(t);
                closeIfClosed();
                return;
            }
            promise.setSuccess();

            //判斷端口是否已經成功,成功則觸發fireChannelActive事件
            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }
        }

最後調用了子類的dobind(),在nioServerSocketChannel中實現

    //調用ServerSocketChannel的bind方法
    @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        //
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
最後無非調用底層ServerSocketChannel的bind方法~已經跑到jdk層面了









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章