[NIO] selector 為什么無限觸發(fā)就緒事件

1.問題引入
2.定位問題


1.問題引入

1.1 寫一個NIO的demo出現(xiàn)問題了,不停的發(fā)出 accept ready 事件,很難理解為啥呀?
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8081));
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        while (true){
            int count = selector.select();
            if(count == 0){
                System.out.println("傳說中的空輪循");
                continue;//
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey selectorKey = iterator.next();
                if(selectorKey.isReadable()){
                    System.out.println("read");
                } 
                if(selectorKey.isAcceptable()){
                    System.out.println("accept" );
                }
                if(selectorKey.isConnectable()){
                    System.out.println("connectable" );
                }
                if(selectorKey.isWritable()){
                    System.out.println("write");
                }
                iterator.remove();
            }
        }

后百度到 Java NIO 一直接收OP_ACCEPT的問題 這個帖子,后對比發(fā)現(xiàn)原來自己沒對accept獲取的數(shù)據(jù)做處理,所以就會出現(xiàn)這種死輪詢的情況,但是如果我不處理,不是應(yīng)該放棄這個事件嗎,為什么會重復(fù)出現(xiàn)這個事件?但總之找到解決方法,繼續(xù)往前試試

1.2 修正過后,我發(fā)現(xiàn)控制臺一直打印writer事件。。
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8081));
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);



        while (true){
            int count = selector.select();
            if(count == 0){
                System.out.println("傳說中的空輪循");
                continue;//
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey selectorKey = iterator.next();
                if(selectorKey.isReadable()){
                    System.out.println("read");
                }
                if(selectorKey.isAcceptable()){
                    //====================== 修正代碼 ======================

                    ServerSocketChannel channel = (ServerSocketChannel)selectorKey.channel();
                    socketChannel.configureBlocking(false);
                    SocketChannel socketChannel = channel.accept();
                    socketChannel.register(selector,
                            SelectionKey.OP_READ|SelectionKey.OP_CONNECT|SelectionKey.OP_WRITE);

                    //====================== 修正代碼 ======================
                    System.out.println("accept" );
                }
                if(selectorKey.isConnectable()){
                    System.out.println("connectable" );
                }
                if(selectorKey.isWritable()){
                    System.out.println("write");
                }
                iterator.remove();
            }
        }
    }

為啥會一直執(zhí)行writer ready事件呀,我使用的telnet 連接server的,莫非telnet會一直發(fā)送數(shù)據(jù),但是實(shí)驗后發(fā)現(xiàn)telnet并沒有一直發(fā)送數(shù)據(jù),而且即使telnet一直發(fā)送數(shù)據(jù)那我收到的也應(yīng)該是read ready事件,而不應(yīng)該是writer ready事件呀。
聯(lián)想到一開始解決accept ready事件的方法,我想到莫非是我沒有writer 數(shù)據(jù)所導(dǎo)致的? 但是為什么要我要writer數(shù)據(jù)?框架不可能強(qiáng)制我writer數(shù)據(jù)呀。

百思不得其解,于是百度到 Java NIO(6): Selector ,和作者demo對比后發(fā)現(xiàn),作者并沒有注冊寫事件,所以作者不會一直writer。
我的代碼 : socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE)
作者代碼:socketChannel.register(selector, SelectionKey.OP_READ);

現(xiàn)在我更加困惑了:

  1. 為什么不調(diào)用ServerSocketChannel.accept() 就會一直觸發(fā)accept ready 事件?
  2. 為什么我的代碼會一直觸發(fā) writer ready事件?
  3. 為什么別人的代碼,在SocketChannel上不注冊writer ready、connect ready事件?

2.定位問題

其實(shí)問題的關(guān)鍵就在于 無限觸發(fā)事件,為什么會無限觸發(fā)事件?
百度 "NIO無限觸發(fā)事件" 最終得到關(guān)鍵的一則博客 NIO網(wǎng)絡(luò)編程中重復(fù)觸發(fā)讀(寫)事件,作者的疑問居然和我非常接近,而最終作者找到了問題的關(guān)鍵:水平觸發(fā)還是邊緣觸發(fā),這也是本文的核心。

引入幾個鏈接:
java nio使用的是水平觸發(fā)還是邊緣觸發(fā)?
epoll 水平觸發(fā)與邊緣觸發(fā)

由上面的連接我們可以得出以下結(jié)論:

  1. 觸發(fā)的方式有兩種
    a) 水平觸發(fā)(level-triggered,也被稱為條件觸發(fā))LT: 只要滿足條件,就觸發(fā)一個事件(只要有數(shù)據(jù)沒有被獲取,內(nèi)核就不斷通知你)
    b) 邊緣觸發(fā)(edge-triggered)ET: 每當(dāng)狀態(tài)變化時,觸發(fā)一個事件。
  2. 水平觸發(fā):
    a) 對于讀操作 :只要內(nèi)核緩沖區(qū)內(nèi)容不為空,LT模式返回讀就緒。
    b) 對于寫操作 :只要內(nèi)核緩沖區(qū)還不滿,LT模式會返回寫就緒。
  3. Java NIO屬于水平觸發(fā),即條件觸發(fā)

由此我們對問題進(jìn)行分析:

  1. 為什么不調(diào)用ServerSocketChannel.accept() 就會一直觸發(fā)accept ready 事件?
    因為java NIO 事件觸發(fā)屬于水平觸發(fā) ,所以如果我們不清理掉"accept"內(nèi)容,就會一直觸發(fā) accpet ready 事件

  2. 為什么我的代碼會一直觸發(fā) writer ready事件?
    因為內(nèi)核緩沖區(qū)還不滿,所以一直寫就緒

  3. 為什么別人的代碼,在SocketChannel上不注冊writer ready、connect ready事件?
    因為注冊writer ready沒有必要,且只要內(nèi)核緩沖區(qū)還不滿就會一直寫就緒。
    而后面我們發(fā)現(xiàn),connect ready事件是針對客戶端而言,相當(dāng)于服務(wù)器端accpet ready事件,也就是說通過ServerSocketChannel#accept()獲得獲取的SocketChannel對象是不需要注冊 connect ready事件。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容