Netty源碼分析-Server端啟動

本文主要對Netty中Server端啟動進行分析甚颂,分析Server端是如何綁定端口刁品,初始化Selector傀蚌,啟動NioEventLoop,并最終實現(xiàn)Reactor模式的脱衙。
首先看一段經(jīng)典的Server啟動代碼扛点。可以看到該過程中實例化了兩個EventLoopGroup縣城組:bossGroup岂丘、workerGroup陵究。可以看到這個Server采用的是Reactor的多線程模式奥帘。創(chuàng)建的SimpleNettyServerHandler是通過childHandler()方法加入到workerGroup線程去執(zhí)行的铜邮。

    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.會調(diào)用initAndRegister()初始化并注冊一個Channel松蒜,此處的Channel是一個NioServerSocketChannel,該方法返回一個ChannelFuture已旧。
2.判斷ChannelFuture是否已經(jīng)執(zhí)行完成秸苗,如果執(zhí)行完成會調(diào)用doBind0()方法;如果沒有執(zhí)行完成运褪,也會通過添加監(jiān)聽器等待執(zhí)行完成后再調(diào)用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()方法檀咙。通過函數(shù)名也大致可以看出該方法工作分為兩部分: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)的實現(xiàn)璃诀。首先對Channel設(shè)置了ChannelOption和AttributeKey弧可,然后會執(zhí)行最重要的一步:在Channel的pipeline中添加ChannelHandler。具體ChannelPipeline的講解會在單獨的文章劣欢,可以看到該ChannelHandler的最終實現(xiàn)是一個ServerBootstrapAcceptor棕诵,通過名字就可以看出這是一個專門做Acceptor的handler裁良,具體對它的分析可以放在文章的后邊。值得注意的是給pipeline添加ServerBootstrapAcceptor的過程是通過ch.eventLoop().execute(new Runnable()執(zhí)行的校套,ch.eventLoop()返回實際就是NioEventLoop(具體可見:)趴久,所以實際是通過NioEventLoop中執(zhí)行非I/O任務(wù)執(zhí)行的。

    @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方法搔确,當(dāng)前的階段是Channel已經(jīng)初始化好了彼棍,Channel對應(yīng)的Pipeline也已經(jīng)初始化完成。但是這個Channel與Reactor線程之間還沒有建立聯(lián)系膳算,下邊的register的過程就是將這個Channel在NioEventLoop線程上進行注冊座硕。
注冊的核心代碼在AbstractChannel$AbstractUnsafe.register方法中,下邊的這段堆棧信息展示了是如何調(diào)到這個方法的:

"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方法的實現(xiàn)非常簡單涕蜂,會將最終的實際執(zhí)行封裝在方法register0(ChannelPromise promise)中华匾,然后根據(jù)線程狀態(tài)決定是在當(dāng)前線程執(zhí)行還是放在ch.eventLoop().execute(new Runnable()執(zhí)行。
1)執(zhí)行doRegister(),這是最核心的一步机隙,具體的實現(xiàn)在類io.netty.channel.nio. AbstractNioChannel中蜘拉,后邊有貼源碼實現(xiàn),代碼中可以看到會將javaChannel注冊到NioEventLoop的selector上有鹿,這樣NioEventLoop的selector就可以監(jiān)聽到Channel的I/O事件變化旭旭。
2)執(zhí)行Channel對應(yīng)Pipeline的fireChannelRegistered()方法
3)根據(jù)是否是firstRegister以及isAutoRead()決定是否執(zhí)行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;
                }
            }
        }
    }
}

至此,可以看到葱跋,我們已經(jīng)創(chuàng)建并初始化了NioServerSocketChannel持寄,并且將該Channel注冊到了NioEventLoop的Selector上。實際上娱俺,NioEventLoop的Loop循環(huán)已經(jīng)可以開始監(jiān)聽ServerChannel的I/O事件了稍味。

1.2 dobind分析

由上所知,initAndRegister()之后荠卷,Channel已經(jīng)初始化好模庐,并且對應(yīng)的Selector都已經(jīng)注冊監(jiān)聽,NioEventLoop也已經(jīng)開始Loop循環(huán)∮鸵耍現(xiàn)在還需要將該channel與我們所要監(jiān)聽的port端口進行綁定掂碱。可以看到验庙,端口綁定的操作也是通過channel.eventLoop().execute 執(zhí)行的顶吮。

    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. 啟動狀態(tài)圖

此處貼用別人blog的一張圖來表示一下社牲。啟動的操作一部分由Main線程完成粪薛,另一部分由bossGroup線程(即NioEventLoop線程)完成。
NioEventLoop線程除了執(zhí)行I/O任務(wù)(第二個)搏恤,還執(zhí)行了doRegister()违寿、doBind()湃交、finishPipeline()這三個非I/O任務(wù)。

server啟動狀態(tài)圖

3. ServerBootstrapAcceptor類分析

通過上邊的分析藤巢,我們知道ServerBootstrapAcceptor是Acceptor Pipeline中的一個handler搞莺。由它負責(zé)對NioServerChannel上發(fā)生I/O事件進行處理。譬如掂咒,當(dāng)NioServerChannel上有一個新的連接請求時才沧,會由bossGroup線程的I/O任務(wù)Selector監(jiān)聽到(具體可參見NioEventLoop中I/O任務(wù)的分析過程),并通過調(diào)用 processSelectedKeys()方法绍刮,并最終執(zhí)行到ServerBootstrapAcceptorchannelRead(ChannelHandlerContext ctx, Object msg)方法温圆。
分析一下channelRead(ChannelHandlerContext ctx, Object msg)方法,參數(shù)里邊msg實際會是通過accept與客戶端建立的一個子連接child孩革。具體的實行過程:
1.給這個子連接child的pipeline 增加Handler岁歉,這個Handler就是文章最開始那段代碼中通過childHandler添加的那個Handle。代碼及child.pipeline().addLast(childHandler)
2.給子連接設(shè)定一些屬性childOptions膝蜈、AttributeKey等
3.將子連接child注冊到childGroup中.后續(xù)關(guān)于該子連接的一些I/O事件及其執(zhí)行會在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 {
            ......
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饱搏,隨后出現(xiàn)的幾起案子非剃,更是在濱河造成了極大的恐慌,老刑警劉巖推沸,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件努潘,死亡現(xiàn)場離奇詭異,居然都是意外死亡坤学,警方通過查閱死者的電腦和手機疯坤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來深浮,“玉大人压怠,你說我怎么就攤上這事》晌” “怎么了菌瘫?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長布卡。 經(jīng)常有香客問我雨让,道長,這世上最難降的妖魔是什么忿等? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任栖忠,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庵寞。我一直安慰自己狸相,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布捐川。 她就那樣靜靜地躺著脓鹃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪古沥。 梳的紋絲不亂的頭發(fā)上瘸右,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音岩齿,去河邊找鬼尊浓。 笑死,一個胖子當(dāng)著我的面吹牛纯衍,可吹牛的內(nèi)容都是我干的栋齿。 我是一名探鬼主播蔑穴,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼撮弧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗅蔬?” 一聲冷哼從身側(cè)響起歌亲,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤菇用,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后陷揪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惋鸥,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年悍缠,在試婚紗的時候發(fā)現(xiàn)自己被綠了卦绣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡飞蚓,死狀恐怖滤港,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趴拧,我是刑警寧澤溅漾,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站著榴,受9級特大地震影響添履,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脑又,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一暮胧、第九天 我趴在偏房一處隱蔽的房頂上張望锐借。 院中可真熱鬧,春花似錦叔壤、人聲如沸瞎饲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至妄田,卻和暖如春俺亮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疟呐。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工脚曾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人启具。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓本讥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鲁冯。 傳聞我的和親對象是個殘疾皇子拷沸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容