Netty源碼分析之服務(wù)端啟動(dòng)(ServerBootstrap)

在上一篇文章中對(duì)于客戶端的啟動(dòng)做了闡述,在本文則將對(duì)服務(wù)端的啟動(dòng)做說明坚俗。其實(shí)服務(wù)端和客戶端啟動(dòng)的過程是比較相似的僚楞,如果對(duì)客戶端啟動(dòng)比較了解,那么接下來的旅程將會(huì)比較輕松劈愚。
同樣的,我們先看下服務(wù)端的代碼:

public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
  • EventLoopGroup: 指定了兩個(gè)闻妓,bossGroup和workGroup菌羽,bossGroup是為了處理請(qǐng)求鏈接,而workGroup是為了處理與各個(gè)客戶端的操作由缆。
  • ChannelType: 指定 Channel 的類型. 因?yàn)槭强蛻舳? 因此使用了 NioServerSocketChannel.
  • Handler: 設(shè)置數(shù)據(jù)的處理器注祖。
    接下來讓我們看看服務(wù)端啟動(dòng)的基本流程:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

創(chuàng)建了兩個(gè)NioEventLoopGroup,這兩個(gè)對(duì)象可以看做是傳統(tǒng)IO編程模型的兩大線程組均唉,bossGroup表示監(jiān)聽端口是晨,accept 新連接的線程組,workerGroup表示處理每一條連接的數(shù)據(jù)讀寫的線程組

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)

給引導(dǎo)類配置兩大線程組舔箭,這個(gè)引導(dǎo)類的線程模型也就定型了罩缴。

.channel(NioServerSocketChannel.class)

和客戶端一樣蚊逢,在這里設(shè)置IO模型,為NIO箫章。

.option(ChannelOption.SO_BACKLOG, 100)

這個(gè)是給服務(wù)端的channel配置一些屬性烙荷,SO_BACKLOG表示系統(tǒng)用于臨時(shí)存放已完成三次握手的請(qǐng)求的隊(duì)列的最大長度,如果連接建立頻繁檬寂,服務(wù)器處理創(chuàng)建新連接較慢终抽,可以適當(dāng)調(diào)大這個(gè)參數(shù)。

.handler(new LoggingHandler(LogLevel.INFO))

handler()用于指定在服務(wù)端啟動(dòng)過程中的一些邏輯桶至。

.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
     ChannelPipeline p = ch.pipeline();
     if (sslCtx != null) {
         p.addLast(sslCtx.newHandler(ch.alloc()));
     }
     //p.addLast(new LoggingHandler(LogLevel.INFO));
     p.addLast(serverHandler);
    }
});

我們調(diào)用childHandler()方法昼伴,給這個(gè)引導(dǎo)類創(chuàng)建一個(gè)ChannelInitializer,這里主要就是定義后續(xù)每條連接的數(shù)據(jù)讀寫镣屹,業(yè)務(wù)處理邏輯圃郊。

EventLoopGroup初始化

服務(wù)端的EventLoopGroup初始化和客戶端的是有區(qū)別的,bossGroup是負(fù)責(zé)監(jiān)聽客戶端連接的女蜈,如果它發(fā)現(xiàn)客戶端有連接來到時(shí)持舆,會(huì)初始化各項(xiàng)資源,接著在workerGroup中取出一個(gè)EeventLoop綁定到這個(gè)客戶端中鞭光,處理這個(gè)客戶端的各種請(qǐng)求吏廉。具體的初始化過程在上一篇文章中已經(jīng)有比較詳細(xì)的說明,這里就不再詳述惰许。比較需要注意的是二者是在哪里和channel關(guān)聯(lián)起來的席覆。首先來看下一下路徑:

ServerBootstrap.bind()-->AbstractBootstrap.doBind()-->AbstractBootstrap.initAndRegister()

仔細(xì)看下initAndRegister()方法中的關(guān)鍵代碼:

final ChannelFuture initAndRegister() {
    channel = channelFactory.newChannel();
    init(channel);
    ChannelFuture regFuture = config().group().register(channel);
}

從上述代碼可以看出,這部分和客戶端的實(shí)現(xiàn)如出一轍汹买,要注意的是config().group()返回的是我們初始化EventLoopGroup的哪個(gè)佩伤,這個(gè)從b.group(bossGroup, workerGroup)方法中可以看出:

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup);
        if (childGroup == null) {
            throw new NullPointerException("childGroup");
        }
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        this.childGroup = childGroup;
        return this;
    }

得出結(jié)論: config().group().register(channel);中的group是parentGroup,即bossGroup晦毙,用于負(fù)責(zé)監(jiān)聽客戶端連接生巡。跟蹤register的結(jié)果自然和客戶端的一樣,最終代碼如下:

selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

即是NioServerSocketChannsl與bossGroup關(guān)聯(lián)上见妒,注冊(cè)到一個(gè)selector孤荣。接下來我們需要找到workerGroup是在哪里與NioSocketChannel關(guān)聯(lián)是在哪里。繼續(xù)往下看须揣。

init(channel)

void init(Channel channel) throws Exception {
        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(0));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
        }

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

從上面代碼可以看出盐股,ChannelPipeline會(huì)添加一個(gè)handler ServerBootstrapAcceptor,在ServerBootstrapAcceptor會(huì)重寫一個(gè)方法:channelRead耻卡,請(qǐng)看channelRead方法關(guān)鍵代碼:

childGroup.register(child).addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        if (!future.isSuccess()) {
            forceClose(child, future.cause());
        }
    }
});

這里的childGroup即是currentChildGroup為workGroup疯汁,在這里workGroup會(huì)和某個(gè)channel關(guān)聯(lián),完成channel注冊(cè)到selector卵酪。所以接下來的疑問在于channelRead方法是在哪里調(diào)用的幌蚊。這里可以提下谤碳,會(huì)調(diào)用NioEventLoop.run方法,這個(gè)方法中調(diào)用processSelectedKeys方法溢豆,最后會(huì)調(diào)用到unsafe.read()中的doReadMessages蜒简,在這個(gè)方法中buf.add(new NioSocketChannel(this, ch));再結(jié)合一下代碼可知,最后通過pipeline機(jī)制會(huì)調(diào)用到channelRead沫换。

 int size = readBuf.size();
for (int i = 0; i < size; i ++) {
    readPending = false;
    pipeline.fireChannelRead(readBuf.get(i));
}

handler的添加

.childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
         if (sslCtx != null) {
             p.addLast(sslCtx.newHandler(ch.alloc()));
         }
         //p.addLast(new LoggingHandler(LogLevel.INFO));
         p.addLast(serverHandler);
     }
});

自定義handler的添加過程和客戶端的如出一轍臭蚁,差別在于此處的handler和childHandler分別設(shè)置了handler和childHandler最铁,handler字段與accept過程有關(guān), handler負(fù)責(zé)處理客戶端的連接請(qǐng)求; 而 childHandler 就是負(fù)責(zé)和客戶端的連接的 IO 交互讯赏。看代碼理解:

void init(Channel channel) throws Exception {
        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(0));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
        }

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

在上述的initChannel方法中通過config.handler()獲取到LoggingHandler冷尉,然后加入pipeline中漱挎,然后再加入ServerBootstrapAcceptor



最后形成結(jié)果如下:


然后ServerBootstrapAcceptor.channelRead方法中會(huì)為新建的Channel 設(shè)置 handler 并注冊(cè)到一個(gè)eventLoop。

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

上述的childHandler就是服務(wù)端啟動(dòng)的childHandler雀哨。當(dāng)這個(gè)客戶端連接 Channel 注冊(cè)后, 就會(huì)觸發(fā) ChannelInitializer.initChannel 方法的調(diào)用, 此后的客戶端連接的 ChannelPipeline 狀態(tài)如下:



最后我們來總結(jié)一下服務(wù)器端的 handler 與 childHandler 的區(qū)別與聯(lián)系:

  • 在服務(wù)器 NioServerSocketChannel 的 pipeline 中添加的是 handler 與 ServerBootstrapAcceptor.
  • 當(dāng)有新的客戶端連接請(qǐng)求時(shí), ServerBootstrapAcceptor.channelRead 中負(fù)責(zé)新建此連接的 NioSocketChannel 并添加 childHandler 到 NioSocketChannel 對(duì)應(yīng)的 pipeline 中, 并將此 channel 綁定到 workerGroup 中的某個(gè) eventLoop 中.
  • handler 是在 accept 階段起作用, 它處理客戶端的連接請(qǐng)求.
  • childHandler 是在客戶端連接建立以后起作用, 它負(fù)責(zé)客戶端連接的 IO 交互.


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末磕谅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雾棺,更是在濱河造成了極大的恐慌膊夹,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捌浩,死亡現(xiàn)場離奇詭異放刨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尸饺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門进统,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浪听,你說我怎么就攤上這事螟碎。” “怎么了迹栓?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵掉分,是天一觀的道長。 經(jīng)常有香客問我克伊,道長酥郭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任答毫,我火速辦了婚禮褥民,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘洗搂。我一直安慰自己消返,他們只是感情好载弄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撵颊,像睡著了一般宇攻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倡勇,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天逞刷,我揣著相機(jī)與錄音,去河邊找鬼妻熊。 笑死夸浅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扔役。 我是一名探鬼主播帆喇,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亿胸!你這毒婦竟也來了坯钦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤侈玄,失蹤者是張志新(化名)和其女友劉穎婉刀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體序仙,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡突颊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诱桂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洋丐。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挥等,靈堂內(nèi)的尸體忽然破棺而出友绝,到底是詐尸還是另有隱情,我是刑警寧澤肝劲,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布迁客,位于F島的核電站,受9級(jí)特大地震影響辞槐,放射性物質(zhì)發(fā)生泄漏掷漱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一榄檬、第九天 我趴在偏房一處隱蔽的房頂上張望卜范。 院中可真熱鬧,春花似錦鹿榜、人聲如沸海雪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奥裸。三九已至险掀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間湾宙,已是汗流浹背樟氢。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侠鳄,地道東北人埠啃。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像畦攘,于是被迫代替她去往敵國和親霸妹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子十电,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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