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)這種死輪詢的情況薄坏,但是如果我不處理新啼,不是應該放棄這個事件嗎伤靠,為什么會重復出現(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ù)吃警,但是實驗后發(fā)現(xiàn)telnet并沒有一直發(fā)送數(shù)據(jù)糕篇,而且即使telnet一直發(fā)送數(shù)據(jù)那我收到的也應該是read ready事件,而不應該是writer ready事件呀酌心。
聯(lián)想到一開始解決accept ready事件的方法拌消,我想到莫非是我沒有writer 數(shù)據(jù)所導致的? 但是為什么要我要writer數(shù)據(jù)安券?框架不可能強制我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)在我更加困惑了:
- 為什么不調用ServerSocketChannel.accept() 就會一直觸發(fā)accept ready 事件址貌?
- 為什么我的代碼會一直觸發(fā) writer ready事件铐拐?
- 為什么別人的代碼,在SocketChannel上不注冊writer ready练对、connect ready事件遍蟋?
2.定位問題
其實問題的關鍵就在于 無限觸發(fā)事件,為什么會無限觸發(fā)事件螟凭?
百度 "NIO無限觸發(fā)事件" 最終得到關鍵的一則博客 NIO網絡編程中重復觸發(fā)讀(寫)事件匿值,作者的疑問居然和我非常接近,而最終作者找到了問題的關鍵:水平觸發(fā)還是邊緣觸發(fā)赂摆,這也是本文的核心挟憔。
引入幾個鏈接:
java nio使用的是水平觸發(fā)還是邊緣觸發(fā)?
epoll 水平觸發(fā)與邊緣觸發(fā)
由上面的連接我們可以得出以下結論:
- 觸發(fā)的方式有兩種
a) 水平觸發(fā)(level-triggered,也被稱為條件觸發(fā))LT: 只要滿足條件烟号,就觸發(fā)一個事件(只要有數(shù)據(jù)沒有被獲取绊谭,內核就不斷通知你)
b) 邊緣觸發(fā)(edge-triggered)ET: 每當狀態(tài)變化時,觸發(fā)一個事件汪拥。 - 水平觸發(fā):
a) 對于讀操作 :只要內核緩沖區(qū)內容不為空达传,LT模式返回讀就緒。
b) 對于寫操作 :只要內核緩沖區(qū)還不滿迫筑,LT模式會返回寫就緒宪赶。 - Java NIO屬于水平觸發(fā),即條件觸發(fā)
由此我們對問題進行分析:
為什么不調用ServerSocketChannel.accept() 就會一直觸發(fā)accept ready 事件脯燃?
因為java NIO 事件觸發(fā)屬于水平觸發(fā) 搂妻,所以如果我們不清理掉"accept"內容,就會一直觸發(fā) accpet ready 事件為什么我的代碼會一直觸發(fā) writer ready事件辕棚?
因為內核緩沖區(qū)還不滿欲主,所以一直寫就緒為什么別人的代碼邓厕,在SocketChannel上不注冊writer ready、connect ready事件扁瓢?
因為注冊writer ready沒有必要详恼,且只要內核緩沖區(qū)還不滿就會一直寫就緒。
而后面我們發(fā)現(xiàn)引几,connect ready事件是針對客戶端而言昧互,相當于服務器端accpet ready事件,也就是說通過ServerSocketChannel#accept()獲得獲取的SocketChannel對象是不需要注冊 connect ready事件伟桅。