java學習歷程:NIO為什麼SelectionKey在被輪詢後需要remove()
學習NIO的過程中,對selector選擇器的知識產生了興趣,尤其是關於SelectionKey的輪詢後remove()的問題,博主嘗試簡單地解釋一下NIO如何實現非阻塞的。
首先是客戶端的程式碼:
public void testNonBlockingNIOClient() throws IOException{ //客戶端 //1.獲取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9899)); //2.切換成非阻塞模式 sChannel.configureBlocking(false);//key //3.緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); //4.讀檔案傳送至服務端 buf.put(new Date().toString().getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); sChannel.close(); }
再者是服務端程式碼:
public void testNonBlockingNIOServer() throws IOException{ //服務端 //1.獲取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); //2.切換成非阻塞模式 ssChannel.configureBlocking(false); //3.繫結連線 ssChannel.bind(new InetSocketAddress(9899)); //4.獲取選擇器selector Selector selector = Selector.open(); //5.將通道註冊到選擇器上 ssChannel.register(selector, SelectionKey.OP_ACCEPT);//ops選擇鍵,監控通道什麼狀態 //return SelectionKey //1 讀 OP_READ //4 寫 OP_WRITE //8 連線 OP_CONNECT //16接受 OP_ACCEPT //|連線符監控多個 //6.輪詢式地獲取選擇器上準備就緒的事件 while(selector.select()>0){ //7.獲取當前選擇器中所有註冊的“選擇鍵(已就緒的監聽事件)” Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ //8.獲取準備就緒的事件 SelectionKey sk = it.next(); //9.判斷具體是什麼事件準備就緒 if(sk.isAcceptable()){ //若“接收就緒” SocketChannel SChannel = ssChannel.accept(); //切換非阻塞模式 SChannel.configureBlocking(false); SChannel.register(selector, SelectionKey.OP_READ); }else if(sk.isReadable()){ //若讀就緒 SocketChannel SChannel = (SocketChannel) sk.channel(); ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; while((len = SChannel.read(buf))>0){ buf.flip(); System.out.println(new String(buf.array(),0,len)); buf.clear(); } } //10.重點,取消選擇鍵 it.remove(); } } }
可以發現服務端的最後進行了remove()操作,將SelectionKey從迭代器中刪除了,博主一開始總覺得很納悶,SelectionKey中可是記錄了相關的channel資訊,如果將SelectionKey刪除了,那不就代表著將通道資訊也抹除了嗎,那後續還怎麼繼續獲取通道,說來慚愧,這問題問的確實缺乏水準。
後來博主理了理selector的思路,要知道,一碼事歸一碼事,channel是註冊在selector中的,在後面的輪詢中,是先將已準備好的channel挑選出來,即selector.select(),再通過selectedKeys()生成的一個SelectionKey迭代器進行輪詢的,一次輪詢會將這個迭代器中的每個SelectionKey都遍歷一遍,每次訪問後都remove()相應的SelectionKey,但是移除了selectedKeys中的SelectionKey不代表移除了selector中的channel資訊(這點很重要),註冊過的channel資訊會以SelectionKey的形式儲存在selector.keys()中,也就是說每次select()後的selectedKeys迭代器中是不能還有成員的,但keys()中的成員是不會被刪除的(以此來記錄channel資訊)。
那麼為什麼要刪除呢,要知道,迭代器如果只需要訪問的話,直接訪問就好了,完全沒必要remove()其中的元素啊,查詢了相關資料,一致的回答是為了防止重複處理(大霧),後來又有資訊說明:每次迴圈呼叫remove()是因為selector不會自己從已選擇集合中移除selectionKey例項,必須在處理完通道時自己移除,這樣,在下次select時,會將這個就緒通道新增到已選擇通道集合中,其實到這裡就已經可以理解了,selector不會自己刪除selectedKeys()集合中的selectionKey,那麼如果不人工remove(),將導致下次select()的時候selectedKeys()中仍有上次輪詢留下來的資訊,這樣必然會出現錯誤,假設這次輪詢時該通道並沒有準備好,卻又由於上次輪詢未被remove()的原因被認為已經準備好了,這樣能不出錯嗎?
即selector.select()會將準備好的channel以SelectionKey的形式放置於selector的selectedKeys()中供使用者迭代,使用的過程中需將selectedKeys清空,這樣下次selector.select()時就不會出現錯誤了。