高性能網(wǎng)絡(luò)通信框架Netty-Netty客戶端底層與Java NIO對(duì)應(yīng)關(guān)系

5.1 Netty客戶端底層與Java NIO對(duì)應(yīng)關(guān)系

在講解Netty客戶端程序時(shí)候我們提到指定NioSocketChannel用于創(chuàng)建客戶端NIO套接字通道的實(shí)例先巴,下面我們來看NioSocketChannel是如何創(chuàng)建一個(gè)Java NIO里面的SocketChannel的。

首先我們來看NioSocketChannel的構(gòu)造函數(shù):

    public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
    }

其中DEFAULT_SELECTOR_PROVIDER定義如下:

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

然后繼續(xù)看

//這里的provider為DEFAULT_SELECTOR_PROVIDER
    public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

其中newSocket代碼如下:

    private static SocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a socket.", e);
        }
    }

所以NioSocketChannel內(nèi)部是管理一個(gè)客戶端的SocketChannel的啥么,這個(gè)SocketChannel就是講Java NIO時(shí)候的SocketChannel,也就是創(chuàng)建NioSocketChannel實(shí)例對(duì)象時(shí)候相當(dāng)于執(zhí)行了Java NIO中:

SocketChannel socketChannel = SocketChannel.open();

另外在NioSocketChannel的父類AbstractNioChannel的構(gòu)造函數(shù)里面默認(rèn)會(huì)記錄隊(duì)op_read事件感興趣,這個(gè)后面當(dāng)鏈接完成后會(huì)使用到:


    protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

另外在NioSocketChannel的父類AbstractNioChannel的構(gòu)造函數(shù)里面設(shè)置了該套接字為非阻塞的

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
         ...
        }
    }

下面我們看Netty里面是哪里創(chuàng)建的NioSocketChannel實(shí)例栖榨,哪里注冊(cè)到選擇器的靡挥。
下面我們看下Bootstrap的connect操作代碼:

    public ChannelFuture connect(InetAddress inetHost, int inetPort) {
        return connect(new InetSocketAddress(inetHost, inetPort));
    }

類似Java NIO傳遞了一個(gè)InetSocketAddress對(duì)象用來記錄服務(wù)端ip和端口:

    public ChannelFuture connect(SocketAddress remoteAddress) {
       ...
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }

下面我們看下doResolveAndConnect的代碼:

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        //(1)
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();

        if (regFuture.isDone()) {
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
             //(2)
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } 
            ...
       }
}

首先我們來看代碼(1)initAndRegister:

     final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            //(1.1)
            channel = channelFactory.newChannel();
            //(1.2)
            init(channel);
        } catch (Throwable t) {
            ...
        }
        //(1.3)
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
}

其中(1.1)作用就是創(chuàng)建一個(gè)NioSocketChannel的實(shí)例,代碼(1.2)是具體設(shè)置內(nèi)部套接字的選項(xiàng)的蜈抓。

代碼(1.3)則是具體注冊(cè)客戶端套接字到選擇器的启绰,其首先會(huì)調(diào)用NioEventLoop的register方法,最后調(diào)用NioSocketChannelUnsafe的register方法:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ...
            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                   ...
                }
            }
 }

其中 register0內(nèi)部調(diào)用doRegister沟使,其代碼如下:

 protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                //注冊(cè)客戶端socket到當(dāng)前eventloop的selector上
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
               ...
            }
        }
 }

到這里代碼(1)initAndRegister的流程講解完畢了委可,下面我們來看代碼(2)的


  public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
          ...
            try {
                ...

                boolean wasActive = isActive();
                if (doConnect(remoteAddress, localAddress)) {
                    fulfillConnectPromise(promise, wasActive);
                } else {
                  。格带。撤缴。
                }
            } catch (Throwable t) {
               ...
            }
  }

其中doConnect代碼如下:

    protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
       ...
        boolean success = false;
        try {
            //2.1
            boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
            //2.2
            if (!connected) {
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

其中2.1具體調(diào)用客戶端套接字的connect方法,等價(jià)于Java NIO里面的叽唱。
代碼2.2 由于connect 方法是異步的屈呕,所以類似JavaNIO調(diào)用connect方法進(jìn)行判斷,如果當(dāng)前沒有完成鏈接則設(shè)置對(duì)op_connect感興趣棺亭。

最后一個(gè)點(diǎn)就是何處進(jìn)行的從選擇器獲取就緒的事件的,具體是在該客戶端套接關(guān)聯(lián)的NioEventLoop里面的做的虎眨,每個(gè)NioEventLoop里面有一個(gè)線程用來循環(huán)從選擇器里面獲取就緒的事件,然后進(jìn)行處理:

 protected void run() {
        for (;;) {
            try {
                ...
               select(wakenUp.getAndSet(false));
                ...
                processSelectedKeys();
                ...
            } catch (Throwable t) {
                handleLoopException(t);
            }
            ...
        }
    }

其中select代碼如下:

 private void select(boolean oldWakenUp) throws IOException {
        Selector selector = this.selector;
        try {
            ...
            for (;;) {
                ...
                int selectedKeys = selector.select(timeoutMillis);
                selectCnt ++;

              ...
        } catch (CancelledKeyException e) {
            ...
        }
 }

可知會(huì)從選擇器選取就緒的事件镶摘,其中processSelectedKeys代碼如下:

 private void processSelectedKeys() {
        ...
     processSelectedKeysPlain(selector.selectedKeys());
        ...
    }

可知會(huì)獲取已經(jīng)就緒的事件集合嗽桩,然后交給processSelectedKeysPlain處理,后者循環(huán)調(diào)用processSelectedKey具體處理每個(gè)事件凄敢,代碼如下:

 private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
       ...
        try {
            //(3)如果是op_connect事件
            int readyOps = k.readyOps();
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);
                //3.1
                unsafe.finishConnect();
            }
            //4
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }
            //5
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

代碼(3)如果當(dāng)前事件key為op_connect則去掉op_connect碌冶,然后調(diào)用NioSocketChannel的doFinishConnect:

    protected void doFinishConnect() throws Exception {
        if (!javaChannel().finishConnect()) {
            throw new Error();
        }
    }

可知是調(diào)用了客戶端套接字的finishConnect方法,最后會(huì)調(diào)用NioSocketChannel的doBeginRead方法設(shè)置對(duì)op_read事件感興趣:

    protected void doBeginRead() throws Exception {
       ...
        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

這里interestOps為op_read,上面在講解NioSocketChannel的構(gòu)造函數(shù)時(shí)候提到過涝缝。

代碼(5)如果當(dāng)前是op_accept事件說明是服務(wù)器監(jiān)聽套接字獲取到了一個(gè)鏈接套接字扑庞,如果是op_read,則說明可以讀取客戶端發(fā)來的數(shù)據(jù)了,如果是后者則會(huì)激活管線里面的所有handler的channelRead方法拒逮,這里會(huì)激活我們自定義的NettyClientHandler的channelRead讀取客戶端發(fā)來的數(shù)據(jù)罐氨,然后在向客戶端寫入數(shù)據(jù)。

5.2 總結(jié)

本節(jié)講解了Netty客戶端底層如何使用Java NIO進(jìn)行實(shí)現(xiàn)的滩援,可見與我們前面講解的Java NIO設(shè)計(jì)的客戶端代碼步驟是一致的栅隐,只是netty對(duì)其進(jìn)行了封裝,方便了我們使用,了解了這些對(duì)深入研究netty源碼提供了一個(gè)骨架指導(dǎo)租悄。

想了解JDK NIO和更多Netty基礎(chǔ)的可以單擊我
更多關(guān)于分布式系統(tǒng)中服務(wù)降級(jí)策略的知識(shí)可以單擊 單擊我
想系統(tǒng)學(xué)dubbo的單擊我
想學(xué)并發(fā)的童鞋可以 單擊我

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谨究,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子恰矩,更是在濱河造成了極大的恐慌记盒,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件外傅,死亡現(xiàn)場離奇詭異纪吮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)萎胰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門碾盟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人技竟,你說我怎么就攤上這事冰肴。” “怎么了榔组?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵熙尉,是天一觀的道長。 經(jīng)常有香客問我搓扯,道長检痰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任锨推,我火速辦了婚禮铅歼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘换可。我一直安慰自己椎椰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布沾鳄。 她就那樣靜靜地躺著慨飘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪译荞。 梳的紋絲不亂的頭發(fā)上瓤的,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音磁椒,去河邊找鬼。 笑死玫芦,一個(gè)胖子當(dāng)著我的面吹牛浆熔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼医增,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼慎皱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叶骨,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤茫多,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后忽刽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體天揖,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年跪帝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了今膊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(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,870評(píng)論 3 333
  • 文/蒙蒙 一献起、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镣陕,春花似錦谴餐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹊碍,卻和暖如春厌殉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侈咕。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工公罕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耀销。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓楼眷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子罐柳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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