Netty系列(四):NioServerSocketChannel注冊

前言

本文主要介紹的是服務端NioServerSocketChannel創(chuàng)建和注冊流程以及客戶端連接到服務端后的NioSocketChannel的創(chuàng)建和注冊流程馅闽,這兩步都是很關鍵的歌亲。在介紹的過程中鄙信,中間會穿插著進行ChannelHandler與ChannelPipeline的一些簡單的介紹昆著。


服務端代碼

上面的代碼段我已經添加了詳細的注釋狐榔,具體的注冊流程得從我標紅的bind這個方法開始暂筝,我們隨著這條線追蹤一下拂共,會發(fā)現(xiàn)最終會調用到doBind方法,里面有個initAndRegister函數(shù)料饥,從這里開始就正式進入創(chuàng)建注冊流程了蒲犬。


initAndRegister函數(shù)

在這個函數(shù)中,我們主要關注三個點:

  1. 調用NioServerSocketChannel的無參構造函數(shù)進行其實例的初始化岸啡。
  2. 進行Option以及Attr的設置原叮,這個方法里面,大家可以關注一下我下面圈紅的ServerBootstrapAcceptor這個類巡蘸,它會被注冊到NioServerSocketChannel的pipeline中去奋隶,后面SocketChannel的注冊流程會依靠這個類做一些操作,大家先留個印象即可赡若,重點达布。
  3. 開啟NioEventLoop的線程,并執(zhí)行register操作逾冬。

我們直接從第三步開始說黍聂,涉及到前面的知識點我后面會一一解釋。

server_channel_register.png

上圖的第二步判斷操作eventLoop.inEventLoop()這一步實際上判斷的是
this.thread == Thread.currentThread()身腻,而this.thread僅僅只是在線程開啟的時候賦值過一次产还,所以我上面說,只要線程已經開啟嘀趟,這類注冊任務便可以直接執(zhí)行脐区,省去了隊列的push和pull的過程。(線程的開啟可以參考我的Netty系列(三))她按。

下面開始說注冊的核心函數(shù)register0牛隅。


register0 核心函數(shù)

  1. doRegister()這個方法在seletor上注冊了NioServerSocketChannel實例,但是沒有綁定任何興趣事件酌泰。我們要知道媒佣,一個ServerSocketChannel要想接受客戶端的連接必須要綁定一個Accept的興趣事件的,所以這里的注冊流程還是不完整的陵刹,后面肯定有方法將這個坑給填上默伍。注冊代碼如下:
    selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
  2. 第二步的主要功能是將handler在咱們的pipeline這個管道中串聯(lián)起來衰琐,方便后面的業(yè)務處理流程也糊。按咱們最開始構造的服務端的代碼,目前pipeline中的handler的順序應該如下圖所示:

在經過pipeline.invokeHandlerAddedIfNeeded()這個方法的調用之后羡宙,pipeline中的管道應該如下圖所示:

調用這個方法的流程一步步點進去不難發(fā)現(xiàn)狸剃,實際上調用的是你重寫handler之后的handlerAdded方法,而ChannelInitializer這個類的handlerAdded方法又調用的本身的initChannel方法:

所以接下來就會走這個流程:


這段代碼是不是很熟悉狗热,我在上面說initAndRegister這個函數(shù)的時候也有用到這塊代碼的捕捂。通過上面這段簡單的流程瑟枫,我們可以對handler是怎樣和pipeline組合到一起有了一個大概的了解。

這里給大家補充個小知識點:

數(shù)據從HeadContext進來指攒,按pipeline從前往后找InboundHandler處理業(yè)務慷妙;但是發(fā)出去時,從TailContext開始允悦,按pipeline從后往前找OutboundHandler處理業(yè)務,這兩者剛好相反膝擂。所以咱們編解碼的handler都要添加在pipeline的最前面,也就是head后面的位置隙弛,這樣data進來就可以進行decode架馋,出去就encode。

  1. 前面介紹過只是將channel注冊到selector上面了全闷,但是未綁定興趣事件叉寂。這個第三步就是一個填坑的操作,將ServerSocketChannel所需要的興趣事件給補充上了总珠。fireChannelActive方法調用的是handler中的channelActive方法屏鳍。咱們看一下HeadContext中的channelActive方法,注冊興趣事件時在我圈紅的方法里面完成的:

咱們看下調用流程:


順著這條鏈路走下去局服,最終調用到一個doBeginRead的方法钓瞭,關注兩個點,一個是selectionKey,一個是readInterestOp淫奔。前者是在上面的第一步
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this)過程中進行的賦值山涡,后者是在調用NioServerSocketChannel的無參構造函數(shù)時進行的賦值:

在通過位運算if ((interestOps & readInterestOp) == 0)判斷該興趣事件如果尚未被注冊的話便進行注冊興趣事件。所以這里會注冊一個OP_ACCEPT興趣事件唆迁。

這樣鸭丛,NioServerSocketChannel的注冊流程就結束了。也能正常接收客戶端發(fā)過來的連接請求了唐责。


NioServerSocketChannel注冊總結圖

NioServerSocketChannel.png

上面是整理的是NioServerSocketChannel從initAndRegister開始的注冊流程圖系吩。


NioSocketChannel創(chuàng)建及注冊

NioSocketChannel的注冊流程咱們從processSelectedKey這個方法開始看,上一篇文章有介紹這個方法里面實際上是根據不同的興趣事件做不同的處理妒蔚,這里我們關注下OP_ACCEPT興趣事件。

咱們完整的看看這個read方法:

        public void read() {
            //判斷NioEventLoop是否已經開啟線程了
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            //取出 與NioServerSocketChannel綁定的pipeline
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        // 生成NioSocketChannel實例
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    //沿著pipeline從head-->tail調用channelRead方法
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                // 清除處理過后的消息月弛,供下次接收使用
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
  1. 看下doReadMessages這個方法:

這一塊就是調用serverSocketChannel.accept()獲取SocketChannel肴盏,這一塊不清楚的可以了解一下JAVA NIO相關的知識點。獲取SocketChannel實例后包裝成為NioSocketChannel實例帽衙,然后塞到集合中供后面進行處理菜皂。

  1. 看下pipeline.fireChannelRead(readBuf.get(i))這個方法,從第一步中咱們可以知道readBuf.get(i)得到的就是一個NioSocketChannel實例厉萝,然后在pipeline中從head-->tail依次執(zhí)行handler中的channelRead方法恍飘。

現(xiàn)在的pipeline如下(不熟悉這張圖的話可以翻到前面去看一下):


實際上主要看ServerBootstrapAcceptor的channelRead方法:

這個流程大家應該不會很陌生了吧榨崩!

1.給NioSocketChannel綁定的pipeline上添加handle。
2.進行Option以及Attr的設置章母。
3.進行register注冊操作母蛛。

后續(xù)的register操作和NioServerSocketChannel差不了多少,就在注冊興趣事件時一個是注冊的OP_ACCEPT乳怎,另一個注冊的是OP_READ彩郊。


NioSocketChannel的創(chuàng)建流程圖

NioSocketChannel.png

上面是整理的是NioSocketChannel的注冊流程圖,只畫到register處,后續(xù)具體的注冊流程可以參考NioServerSocketChannel的注冊流程蚪缀。


End

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末秫逝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子询枚,更是在濱河造成了極大的恐慌违帆,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件金蜀,死亡現(xiàn)場離奇詭異刷后,居然都是意外死亡,警方通過查閱死者的電腦和手機廉油,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門惠险,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抒线,你說我怎么就攤上這事班巩。” “怎么了嘶炭?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵抱慌,是天一觀的道長。 經常有香客問我眨猎,道長抑进,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任睡陪,我火速辦了婚禮寺渗,結果婚禮上,老公的妹妹穿的比我還像新娘兰迫。我一直安慰自己信殊,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布汁果。 她就那樣靜靜地躺著涡拘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪据德。 梳的紋絲不亂的頭發(fā)上鳄乏,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天跷车,我揣著相機與錄音,去河邊找鬼橱野。 笑死朽缴,一個胖子當著我的面吹牛,可吹牛的內容都是我干的仲吏。 我是一名探鬼主播不铆,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼裹唆!你這毒婦竟也來了誓斥?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤许帐,失蹤者是張志新(化名)和其女友劉穎劳坑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體成畦,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡距芬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了循帐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片框仔。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拄养,靈堂內的尸體忽然破棺而出离斩,到底是詐尸還是另有隱情,我是刑警寧澤瘪匿,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布跛梗,位于F島的核電站,受9級特大地震影響棋弥,放射性物質發(fā)生泄漏核偿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一顽染、第九天 我趴在偏房一處隱蔽的房頂上張望漾岳。 院中可真熱鬧,春花似錦粉寞、人聲如沸尼荆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翔悠,卻和暖如春业崖,著一層夾襖步出監(jiān)牢的瞬間野芒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工双炕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狞悲,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓妇斤,卻偏偏與公主長得像摇锋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子站超,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容