Nety線程模型2(Accept篇)

概述

Netty的線程模型沒什么出彩的地方,舊瓶裝新酒晰骑,其就是基于Reactor模式

Reactor模式結構

首先用一張許多人都看過的圖來開始說明Reactor模式

Netty Reactor.jpeg

這張圖估計在許多博客和帖子都會看到继薛,但是許多博客卻沒有詳細說明及解釋這張圖在netty的架構上反應出來

  • Reactor模式角色定義
    1:MainReactor: 負責響應client的連接請求乞娄,并建立連接惧互,可以有1個或者多個,維護一個獨立的NIO Selector
    2:SubReactor: 負責對client的讀寫請求進行處理鳍刷,可以1個或者多個,并也維護一個獨立的NIO Selector
    3:Acceptor: 負責MainReactor和SubReactor的橋梁左右理盆,已經準備好的連接轉發(fā)到SubReactor中進行處理

  • Netty中Reactor模式角色定義
    1:MainReactorEventLoopGroup痘煤,在Netty4以前叫做BossGroup;
    2:SubReactor:EventLoopGroup, 在Netty4以前叫做WorkGroup猿规;
    3:Acceptor:ServerBootstrapAcceptor:一個系統(tǒng)自帶的ChannelInboundHandler事件攔截器衷快,真正的將已準備好的Channel注冊到SubReactor中;

SubReactor:EventLoopGroup

subReactor的任務比較簡單姨俩,接收Acceptor的Channel蘸拔,后將Channel重新進行注冊,并觸發(fā)自定義的Handler來處理邏輯
1)接收Acceptor傳遞過來的Channel通道
2)注冊到相應的selector环葵。

private void register0(ChannelPromise promise) {
            try {
                doRegister();
                registered = true;
                promise.setSuccess();
                pipeline.fireChannelRegistered();
                if (isActive()) {
                    pipeline.fireChannelActive();
                }
            } catch (Throwable t) {
            }
        }

3)調用eventLoop.execute用以執(zhí)行注冊任務
4)啟動子線程调窍。即啟動了subReactor

注意:pipeline.fireChannelRegistered()觸發(fā)的事件,其實現(xiàn)原理就是初始化不同的ChannelInitializer對象张遭,對不同類型的Channel添加不同的攔截處理

  • 啟動時邓萨,Channel是NioServerSocketChannel,調用的是ServerBootstrapAcceptor
  • 連接時菊卷,Channel是NioSocketChannel缔恳,調用的是用戶自定義的InboundHandler

例如:

 ServerBootstrap.childHandler(new GearmanServerInitializer())//此用戶自定義的的ChannelInitializer

public class GearmanServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        pipeline.addLast("decoder", new Decoder());
        pipeline.addLast("encoder", new Encoder());
        pipeline.addLast("handler", new PacketHandler(networkManager));
    }
}
Acceptor:ServerBootstrapAcceptor
public void channelRead(ChannelHandlerContext ctx, Object msg) {
            Channel child = (Channel) msg;
            child.pipeline().addLast(childHandler);
            // ...... 省略無關代碼
            try {
                   childGroup.register(child);
            } catch (Throwable t) {
              // ......省略無關代碼
            }
        }

說明:
當Channel已經Ready后,就會ServerBootstrapAcceptor#channelRead
1:首先把用戶自定義的handler注冊到pipleline中
2:將已準備好的Channel與childGroup的烁,觸發(fā)點就是childGroup.register(child);

如果看了上篇文章褐耳,會發(fā)現(xiàn)兩者都是存在注冊通道的原理,其實是不同的

  1. 在server啟動時渴庆,通過回調bind的監(jiān)聽會把Selector注冊事件改為electionKey.OP_ACCEPT
  2. 而當有連接進來的時候铃芦,通過重新注冊又把Selector注冊事件改為了0
    在這一點有點勞民傷財?shù)奈兜馈#ㄆ鋵嵅皇墙罄祝谙旅鏁iT有注意點提到這點)
MainReactor:EventLoopGroup

如果看了上篇文章刃滓,應該知道在server啟動時,會啟動MainReactor耸弄,一直循環(huán)執(zhí)行IO任務和非IO任務

 protected void run() {
        for (;;) {
            oldWakenUp = wakenUp.getAndSet(false);
            try {
                if (hasTasks()) {
                    selectNow();
                } else {
                    select();
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                }
                cancelledKeys = 0;
                final long ioStartTime = System.nanoTime();
                needsToSelectAgain = false;
                if (selectedKeys != null) {
                    processSelectedKeysOptimized(selectedKeys.flip());
                } else {
                    processSelectedKeysPlain(selector.selectedKeys());
                }
                final long ioTime = System.nanoTime() - ioStartTime;
                final int ioRatio = this.ioRatio;
                runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                //..........省略無關代碼
            } catch (Throwable t) {
               //...........省略無關代碼
            }
        }
    }

在上篇文章主要提的是runAllTasks這個方法咧虎,主要執(zhí)行非IO任務
這里主要是來說明下IO任務,selectedKeys不為空
if (selectedKeys != null) {
processSelectedKeysOptimized(selectedKeys.flip());
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
執(zhí)行processSelectedKeysOptimized(selectedKeys.flip());

private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
        for (int i = 0;; i ++) {
            final SelectionKey k = selectedKeys[i];
            if (k == null) {
                break;
            }
            final Object a = k.attachment();
            if (a instanceof AbstractNioChannel) {
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }
            if (needsToSelectAgain) {
                selectAgain();
                selectedKeys = this.selectedKeys.flip();
                i = -1;
            }
        }
    }

注意的地方就是 final Object a = k.attachment();這個attachment是從哪里來的,看如下selectionKey =javaChannel().register(eventLoop().selector, 0, this);

  protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
               
            }
        }
    }
  • 對于當server啟動時计呈,由于該當前對象是NioServerSocketChannel
  • 對于當連接進來是砰诵,由于當前對象是NioSocketChannel

在注冊整個Selector選擇器的時候,把當前通道(Channel)也注冊進去了捌显,上面那個勞民傷財其實是句玩笑茁彭,在這里體現(xiàn)出兩次注冊的用意來了

繼續(xù)以上processSelectedKeysOptimized,其中processSelectedKey就是處理網(wǎng)絡Io事件扶歪,把該事件發(fā)給Acceptor的主要觸發(fā)點,而有點要

代碼如下:

private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    
        int readyOps = -1;
        try {
            readyOps = k.readyOps();
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
                if (!ch.isOpen()) {
                    return;
                }
            }
            //........省略無關代碼
        } catch (CancelledKeyException e) {
           //.......省略無關代碼
        }
    }

而組裝channel理肺,bytebuffer等網(wǎng)絡數(shù)據(jù)是在NioMessageUnsafe#read()中

        public void read() {
            //... 省略無關代碼
            try {
                for (;;) {
                    int localRead = doReadMessages(readBuf);
                    // .......省略無關代碼
                }
            } catch (Throwable t) {
            }
            for (int i = 0; i < readBuf.size(); i ++) {
                pipeline.fireChannelRead(readBuf.get(i));
            }
            readBuf.clear();
            pipeline.fireChannelReadComplete();
             // ..... 省略無關代碼
        }
    }
  • doReadMessages(readBuf);
    把當前請求連接封裝成一個Channel(其實就是NioSocketChannel)
  • pipeline.fireChannelRead(readBuf.get(i));
    通知Acceptor來讀取,其實就是通知ServerBootstrapAcceptor#channelRead
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市妹萨,隨后出現(xiàn)的幾起案子年枕,更是在濱河造成了極大的恐慌,老刑警劉巖乎完,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熏兄,死亡現(xiàn)場離奇詭異,居然都是意外死亡囱怕,警方通過查閱死者的電腦和手機霍弹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娃弓,“玉大人典格,你說我怎么就攤上這事√ù裕” “怎么了耍缴?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挽霉。 經常有香客問我防嗡,道長,這世上最難降的妖魔是什么侠坎? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任蚁趁,我火速辦了婚禮,結果婚禮上实胸,老公的妹妹穿的比我還像新娘他嫡。我一直安慰自己,他們只是感情好庐完,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布钢属。 她就那樣靜靜地躺著,像睡著了一般门躯。 火紅的嫁衣襯著肌膚如雪淆党。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天讶凉,我揣著相機與錄音染乌,去河邊找鬼。 笑死懂讯,一個胖子當著我的面吹牛慕匠,可吹牛的內容都是我干的。 我是一名探鬼主播域醇,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了譬挚?” 一聲冷哼從身側響起锅铅,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎减宣,沒想到半個月后盐须,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡漆腌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年贼邓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闷尿。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡塑径,死狀恐怖,靈堂內的尸體忽然破棺而出填具,到底是詐尸還是另有隱情统舀,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布劳景,位于F島的核電站誉简,受9級特大地震影響,放射性物質發(fā)生泄漏盟广。R本人自食惡果不足惜闷串,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筋量。 院中可真熱鬧烹吵,春花似錦、人聲如沸毛甲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玻募。三九已至只损,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間七咧,已是汗流浹背跃惫。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留艾栋,地道東北人爆存。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像蝗砾,于是被迫代替她去往敵國和親先较。 傳聞我的和親對象是個殘疾皇子携冤,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容