Netty解讀 - 1.從ServerBootstrap認(rèn)識(shí)Netty

版權(quán)聲明:本文為博主原創(chuàng)文章敌完,未經(jīng)博主允許不得轉(zhuǎn)載。

摘要

該系列文章主要是分析Netty源碼4.1.16.Final,了解Netty框架的設(shè)計(jì)和其中各種組件的優(yōu)化手段。
這篇文章作為系列的第一篇文章吗氏,以最簡(jiǎn)單的EchoServer為例,從ServerBootstrap開(kāi)始跟蹤其初始化和啟動(dòng)流程雷逆,讓讀者在直觀上對(duì)Netty框架有大概的認(rèn)識(shí)弦讽。

EchoServer

我們從Netty提供的EchoServer開(kāi)始,重要的代碼片段如下:

        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        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(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

ServerBootstrap

ServerBootstrap作為一個(gè)啟動(dòng)輔助類(lèi)关面,通過(guò)他可以很方便的創(chuàng)建一個(gè)Netty服務(wù)端坦袍。從EchoServer可以看出,實(shí)現(xiàn)一個(gè)Server大概分為這樣幾個(gè)步驟:

  1. 設(shè)置bossGroup和workGroup等太。這里可以先理解為兩個(gè)線(xiàn)程池捂齐,bossGroup設(shè)置一個(gè)線(xiàn)程,用于處理連接請(qǐng)求和建立連接缩抡,而workGroup線(xiàn)程池大小默認(rèn)值2*CPU核數(shù)奠宜,在連接建立之后處理IO請(qǐng)求。EventLoopGroup體現(xiàn)了Netty對(duì)線(xiàn)程模型的抽象設(shè)計(jì)瞻想,之后會(huì)獨(dú)立開(kāi)篇介紹压真。
  2. 指定使用NioServerSocketChannel來(lái)處理連接請(qǐng)求。channel(NioServerSocketChannel.class)這段代碼實(shí)際上在設(shè)置channelFactory蘑险,而ServerBootstrap會(huì)通過(guò)channelFactory.newChannel來(lái)生產(chǎn)channel滴肿。這里channelFactory是一個(gè)ReflectiveChannelFactory,顧名思義這個(gè)工廠類(lèi)以反射的方式來(lái)構(gòu)建channel實(shí)例佃迄,而實(shí)例的類(lèi)型就是我們指定的NioServerSocketChannel泼差。
public T newChannel() {
    ...  
        return clazz.getConstructor().newInstance();
    ...
}
  1. 配置TCP參數(shù)。
  2. 配置handler和childHandler呵俏,數(shù)據(jù)處理器堆缘。
  3. ServerBootstrap啟動(dòng)服務(wù)器。

真正的啟動(dòng)過(guò)程由ChannelFuture f = b.bind(PORT).sync();開(kāi)始觸發(fā)普碎。調(diào)用鏈路:ServerBootstrap.bind → AbstractBootstrap.bind → AbstractBootstrap.doBind吼肥,重點(diǎn)分析一下doBind:

private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        ...
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        ...
    }

這個(gè)過(guò)程分為兩個(gè)關(guān)鍵步驟:

  1. initAndRegister - 包括Channel的創(chuàng)建、初始化和注冊(cè)三個(gè)子步驟
  2. doBind0 - Channel綁定到監(jiān)聽(tīng)端口

1.1 NioServerSocketChannel創(chuàng)建

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

這里channelFactory就是上面提到的ReflectiveChannelFactory麻车,他通過(guò)反射來(lái)創(chuàng)建NioServerSocketChannel實(shí)例缀皱,繼續(xù)跟進(jìn)NioServerSocketChannel的構(gòu)造方法:

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

public NioServerSocketChannel() {
      this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

private static ServerSocketChannel newSocket(SelectorProvider provider) {
      ...
      return provider.openServerSocketChannel();
      ...
}

public NioServerSocketChannel(ServerSocketChannel channel) {
      super(null, channel, SelectionKey.OP_ACCEPT);
      config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

因?yàn)楂@取provider的過(guò)程包含同步訪(fǎng)問(wèn)的消耗,所以NioServerSocketChannel緩存了provider并使用provider.openServerSocketChannel()來(lái)創(chuàng)建ServerSocketChannel實(shí)例动猬。

除了創(chuàng)建ServerSocketChannel外唆鸡,還要?jiǎng)?chuàng)建NioServerSocketChannelConfig。ChannelConfig的職責(zé)是統(tǒng)一保存channel配置枣察,并提供讀取和設(shè)置的接口。不同的ChannelConfig具體實(shí)現(xiàn)不一樣,例如NioServerSocketChannelConfig提供的setReuseAddress接口序目,是為ServerSocketChannel設(shè)置useAddress參數(shù)臂痕。

NioServerSocketChannel的構(gòu)造過(guò)程并沒(méi)有這么簡(jiǎn)單,還要繼續(xù)向上看看父類(lèi)的構(gòu)造函數(shù)猿涨。


NioServerSocketChannel類(lèi)圖.png

先來(lái)看AbstractNioChannel的構(gòu)造方法:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    ...
       //設(shè)置channel非阻塞模式
       ch.configureBlocking(false);
    ...
   }
}

接下來(lái)是AbstractChannel類(lèi)的構(gòu)造方法:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

代碼清楚的交代了3件事情:

  1. 為當(dāng)前channel生成ID(由機(jī)器名握童,進(jìn)程名,序列號(hào)叛赚,進(jìn)程號(hào)和隨機(jī)數(shù)組成)澡绩。
  2. 創(chuàng)建Unsafe實(shí)例(NioMessageUnSafe)。所有的UnSafe類(lèi)都主要用于網(wǎng)絡(luò)方面的操作俺附,比如read肥卡,write,close等事镣,Channel關(guān)于網(wǎng)絡(luò)的操作都會(huì)委托給該unsafe來(lái)完成步鉴。
  3. 創(chuàng)建DefaultChannelPipeline,他就是大名鼎鼎的Pipeline機(jī)制璃哟。先從DefaultChannelPipeline構(gòu)造函數(shù)入手:
protected DefaultChannelPipeline(Channel channel) {
     this.channel = ObjectUtil.checkNotNull(channel, "channel");
     ...
     tail = new TailContext(this);
     head = new HeadContext(this);
     head.next = tail;
     tail.prev = head;
}

pipeline內(nèi)部會(huì)組織一個(gè)ChannelHandlerContext的鏈表氛琢,上下行的事件都會(huì)經(jīng)過(guò)這個(gè)鏈表依次處理。但是在構(gòu)造Pipeline時(shí)随闪,就只有兩個(gè)ChannelHandlerContext:HeadContext主要用于處理輸出事件阳似,而TailContext用于處理輸入事件。用戶(hù)添加的Handler也會(huì)被封裝在新的Context然后插入到鏈表中铐伴,參與事件的處理撮奏,這一點(diǎn)后面會(huì)再提到。
先來(lái)看看TailContext構(gòu)造函數(shù):

TailContext(DefaultChannelPipeline pipeline) {
      super(pipeline, null, TAIL_NAME, true, false);
      ...
 }

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
                                  boolean inbound, boolean outbound) {
      this.name = ObjectUtil.checkNotNull(name, "name");
      this.pipeline = pipeline;
      this.executor = executor;
      this.inbound = inbound;
      this.outbound = outbound;
      ordered = executor == null || executor instanceof OrderedEventExecutor;
}

值得注意的是inbound和outbound兩個(gè)屬性盛杰,TailContext(inbound=true挽荡,outbound=false),而HeadContext剛好相反(inbound=false即供,outbound=true)定拟,這兩個(gè)屬性由于標(biāo)識(shí)該Context是用于處理輸入事件還是輸出事件的,當(dāng)事件處理時(shí)會(huì)根據(jù)是事件輸入還是輸出而選擇相應(yīng)的Context來(lái)處理逗嫡。
再來(lái)看HeadContext:

HeadContext(DefaultChannelPipeline pipeline) {
      super(pipeline, null, HEAD_NAME, false, true);
      unsafe = pipeline.channel().unsafe();
      ...
}

與TailContext
相比青自,HeadContext多了unsafe屬性,是從pipeline中的channel中獲取的驱证,即NioSocketChannel構(gòu)造函數(shù)中提到的unsafe對(duì)象延窜。TailContext實(shí)現(xiàn)的很多方法都是空的,不做任何處理抹锄;而HeadContext實(shí)現(xiàn)的方法就比較多了逆瑞,包括bind荠藤,connect,disconnect获高,read哈肖,write等,都是依賴(lài)unsafe來(lái)完成的念秧。

到這里NioServerSocketChannel的創(chuàng)建過(guò)程才算結(jié)束淤井,總結(jié)一下這個(gè)過(guò)程都包含了那些步驟:

  • ServerBootStrap通過(guò)ReflectiveChannelFactory創(chuàng)建NioServerSocketChannel實(shí)例。
  • NioServerSocketChannel實(shí)例內(nèi)部創(chuàng)建了ServerSocketChannel摊趾。
  • 每個(gè)Channel(AbstractChannel)內(nèi)部都會(huì)創(chuàng)建ChannelID币狠,unsafe以及pipeline。

1.2 NioServerSocketChannel初始化

回到AbstractBootstrap.initAndRegister

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

上一節(jié)介紹了channel的創(chuàng)建過(guò)程砾层,接下來(lái)再看ServerBootstrap.init

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

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

該初始化過(guò)程可以劃分為兩個(gè)部分:

  1. 設(shè)置channel的配置參數(shù)和附屬屬性漩绵。
  2. 向channel的pipeline添加handler。這里的handler是通過(guò)ServerBootstrap.handler設(shè)置的梢为,是在連接建立后的通用handler渐行。

重點(diǎn)來(lái)看最后提到的ServerBootstrapAcceptor是什么?首先他是一個(gè)ChannelHandler铸董,也會(huì)參與到事件的處理中祟印,其次事件處理過(guò)程channelRead值得注意:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
      final Channel child = (Channel) msg;
      child.pipeline().addLast(childHandler); 
      ...
         childGroup.register(child).addListener(new ChannelFutureListener() {
                    ...
         });
     ...
}

當(dāng)新的連接SocketChannel建立后,會(huì)被包裝在一個(gè)新建的NioSocketChannel中粟害,之后開(kāi)始觸發(fā)pipeline.fireChannelRead蕴忆,最終經(jīng)過(guò)ServerBootstrapAcceptor.channelRead。在這里開(kāi)始為這個(gè)NioSocketChannel添加childHandler悲幅,而childHandler則是我們?cè)赟erverBootstrap中指定的childHandler套鹅。接著在childGroup(也就是我們配置的workerGroup)中注冊(cè)

1.3 NioServerSocketChannel注冊(cè)

2.1 NioServerSocketChannel綁定

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市汰具,隨后出現(xiàn)的幾起案子卓鹿,更是在濱河造成了極大的恐慌,老刑警劉巖留荔,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吟孙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡聚蝶,警方通過(guò)查閱死者的電腦和手機(jī)杰妓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碘勉,“玉大人巷挥,你說(shuō)我怎么就攤上這事⊙槊遥” “怎么了倍宾?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵雏节,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我凿宾,道長(zhǎng)矾屯,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任初厚,我火速辦了婚禮,結(jié)果婚禮上孙技,老公的妹妹穿的比我還像新娘产禾。我一直安慰自己,他們只是感情好牵啦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布亚情。 她就那樣靜靜地躺著,像睡著了一般哈雏。 火紅的嫁衣襯著肌膚如雪楞件。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天裳瘪,我揣著相機(jī)與錄音土浸,去河邊找鬼。 笑死彭羹,一個(gè)胖子當(dāng)著我的面吹牛黄伊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播派殷,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼还最,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了毡惜?” 一聲冷哼從身側(cè)響起拓轻,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎经伙,沒(méi)想到半個(gè)月后扶叉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡橱乱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年辜梳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泳叠。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡作瞄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出危纫,到底是詐尸還是另有隱情宗挥,我是刑警寧澤乌庶,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站契耿,受9級(jí)特大地震影響瞒大,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搪桂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一透敌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧踢械,春花似錦酗电、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至话瞧,卻和暖如春嫩与,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背交排。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工划滋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人个粱。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓古毛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親都许。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稻薇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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