NIO Select

使用NIO的一個最大優勢就是客戶端於服務器自己的不再是阻塞式的,也就意味着服務器無需通過爲每個客戶端的鏈接而開啓一個線程。而是通過一個叫Selector的輪循器來不斷的檢測那個Channel有消息處理。

簡單來講,Selector會不斷地輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連接接入、讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的Set集合,進行後續的I/O操作。
由於select操作只管對selectedKeys的集合進行添加而不負責移除,所以當某個消息被處理後我們需要從該集合裏去掉。

 

NIO中的選擇器(Selector)的作用就是維護註冊到選擇器中的通道集合,每一個通道與選擇器的關係封裝在選擇鍵(SelectionKey)中,實際上可以認爲選擇器維護的是選擇鍵集合。創建Selector對象使用Selector.open()。

Selector類主要維護三個集合:

  • Registered key set(已註冊鍵集合):調用Selector的keys()方法可以獲取
  • Selected key set(已選擇鍵集合):調用Selector的selectedKeys()方法獲取
  • Cancelled key set(已取消鍵集合):已取消鍵集合是Selector對象的私有成員,外部無法訪問

只有繼承了SelectableChannel的類才能註冊到選擇器中,並且只有非阻塞模式的通道才能註冊到選擇器,如下代碼:

                //創建一個套接字服務器,並註冊到選擇器
		//創建選擇器
		Selector selector = Selector.open();
		//創建Socket服務器通道
		ServerSocketChannel ssc = ServerSocketChannel.open();
		//綁定65535端口
		ssc.socket().bind(new InetSocketAddress(65535));
		//設置通道爲非阻塞模式
		ssc.configureBlocking(false);
		//將通道註冊到選擇器,指定通道興趣是等待接收連接
		SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);

實際使用中,一般使用while循環輪詢獲取註冊到選擇器中通道感興趣的操作,如下代碼:

while (true) {
			int n = selector.select();
			if (n > 0) {
				Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
				while (iter.hasNext()) {
					SelectionKey keyy = iter.next();
					iter.remove();
					// ......
				}
			}
		}

select()方法的作用是選擇註冊到選擇器中通道感興趣的鍵,此方法是阻塞的,直到有感興趣的事件發生;還可以使用select(10000)方法設置選擇鍵的超時時間,單位是Millisecond;還可以使用selectNow(),此方法是非阻塞,若沒有通道就緒會立即返回0。

        不需要使用Selector時,調用close()方法可以關閉選擇器,關閉選擇器後,所有註冊到其中的選擇鍵會被設置爲無效狀態。

        關閉選擇器後,試圖調用它的方法會拋出ClosedSelectorException,這是一個非運行時異常,所有在使用時有必要檢查選擇器是否打開,使用isOpen()方法。

註冊一個通道時可以在這個選擇鍵上設置一個Object對象,比如在接受連接操作中設置,在讀操作中獲取

                SelectionKey keyy = iter.next();
		iter.remove();
		if(keyy.isAcceptable()){
			ServerSocketChannel channel = (ServerSocketChannel) keyy.channel();
			SocketChannel sc = channel.accept();
			sc.register(selector, SelectionKey.OP_READ, "hello");
		}
		if(keyy.isReadable()){
			//attach的值爲"hello"
			Object attach = keyy.attachment();
			SocketChannel sc = (SocketChannel) keyy.channel();

		}

 SelectionKey(選擇鍵對象)提供了下面方法:

  • channel():獲取關聯的通道(SelectableChannel)
  • selector():獲取關聯的選擇器(Selector)
  • isValid():驗證維護選擇器與通道關係的SelectionKey是否有效
  • cancel():取消鍵,調用一個已取消的鍵的方法將拋出CancelledKeyException
  • interestOps():獲取這個key的興趣(可選擇操作)集合
  • interestOps(int ops):設置這個key的興趣
  • readyOps():返回這個key準備好的操作集合(興趣集合)
  • isReadable():檢查選擇鍵的興趣是否爲可讀,實際上是通道的興趣,選擇鍵維護通道與選擇器關係
  • isWritable():檢查選擇鍵的興趣是否爲可寫
  • isConnectable():檢查選擇鍵的興趣是否爲連接
  • isAcceptable():檢查選擇鍵的興趣是否爲接受
  • attach(Object ob):設置一個Object數據到此key上
  • attachment():獲取設置的Object數據

Selector(選擇器)提供了下面方法:

  • open():打開一個選擇器
  • isOpen():檢查一個選擇器實例是否打開
  • provider():返回一個SelectorProvider
  • keys():返回註冊鍵集合
  • selectedKeys():返回已選擇鍵集合
  • selectNow():立刻執行選擇,非阻塞,若沒有已準備好的通道則立即返回0
  • select(long timeout):執行選擇,超過指定毫秒數則返回
  • select():執行選擇,會一直阻塞直到有準備就緒的通道
  • wakeup():停止選擇
  • close():關閉選擇器

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章