Java Nio選擇器Selector
Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,並能夠知曉通道是否為諸如讀寫事件做好準備的元件。這樣,一個單獨的執行緒可以管理多個channel,從而管理多個網路連線 ,減少伺服器的效能開銷。
建立Selector
通過Selector 提供的靜態方法建立 Selector selector = Selector.open();
通過SelectorProvider獲取選擇器
SelectorProvider selectorProvider = SelectorProvider.provider(); Selector selector = selectorProvider.openSelector();
將通道註冊到選擇器上
通過ServerSocketChannel.register(Selector sel, int ops)方法註冊的selecor中
//獲取通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //切換到非堵塞模式 serverSocketChannel.configureBlocking(false); //繫結埠號 serverSocketChannel.bind(new InetSocketAddress(8080)); //獲取選擇器 Selector selector = Selector.open(); //將通道註冊到選擇器上,並且指定“監聽接收事件” SelectionKey key = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
與selector使用,channel必須處於非堵塞狀態下,FileChannel無法切換到非堵塞狀態下將不能與selector一起使用。
register()中的第二個引數表示通道監聽的事件一共四種狀態
- SelectionKey.OP_CONNECT 連線
- SelectionKey.OP_ACCEPT 接收
- SelectionKey.OP_READ 讀
- SelectionKey.OP_WRITE 寫
單一個通道監聽多種事件時使用位或
多種事件如: SelectionKey.OP_CONNECT|SelectionKey.OP_ACCEPT
Selector的select()方法
Selector中註冊了一或多個通道,就可以呼叫幾個過載的select()方法。這些方法返回通道的事件(如連線、接受、讀或寫)已經準備就緒的那些通道。換句話說,如果你對“讀就緒”的通道感興趣,select()方法會返回讀事件已經就緒的那些通道。
下面是select()方法:
- int select()
- int select(long timeout)
- int selectNow()
select()
阻塞到至少有一個通道在你註冊的事件上就緒了。
select(long timeout)
和select()一樣,除了最長會阻塞timeout毫秒(引數)。
selectNow()
不會阻塞,不管什麼通道就緒都立刻返回(此方法執行非阻塞的選擇操作。如果自從前一次選擇操作後,沒有通道變成可選擇的,則此方法直接返回零。)。
select()方法返回的int值表示有多少通道已經就緒。亦即,自上次呼叫select()方法後有多少通道變成就緒狀態。如果呼叫select()方法,因為有一個通道變成就緒狀態,返回了1,若再次呼叫select()方法,如果另一個通道就緒了,它會再次返回1。如果對第一個就緒的channel沒有做任何操作,現在就有兩個就緒的通道,但在每次select()方法呼叫之間,只有一個通道就緒了。
selectedKeys()
一旦呼叫了select()方法,並且返回值表明有一個或更多個通道就緒了,然後可以通過呼叫selector的selectedKeys()方法,訪問“選擇健(已就緒的監聽事件)”中的就緒通道。如下所示:
Set selectedKeys = selector.selectedKeys()
使用Selector實現非堵塞Socket
服務端
//1.獲取通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2.切換到非堵塞模式 serverSocketChannel.configureBlocking(false); //3.繫結埠號 serverSocketChannel.bind(new InetSocketAddress(8080)); //4.獲取選擇器 Selector selector = Selector.open(); //5.將通道註冊到選擇器上,並且指定“監聽接收事件” serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); //6輪詢式的獲取選擇器上已經‘準備就緒’的事件 while (selector.select()>0){ //7 。獲取當前選擇器中所有註冊的"選擇健(已就緒的監聽事件)" Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ //8.獲取“準備就緒”的事件 SelectionKey selectionKey = iterator.next(); //9.判斷具體事件,就緒 if (selectionKey.isAcceptable()){ //10.接收就緒,獲取客戶端連線 SocketChannel socketChannel = serverSocketChannel.accept(); //11,切換到非堵塞模式 socketChannel.configureBlocking(false); //12.將客戶端通道註冊到選擇器上 socketChannel.register(selector,SelectionKey.OP_READ); }else if (selectionKey.isReadable()){ //獲取當前選擇器上“讀就緒”狀態的通道 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); //讀取客戶端傳過來的資料 int len = 0; while ((len = socketChannel.read(buffer))>0){ buffer.flip(); System.out.println(new String(buffer.array(),0,len)); buffer.clear(); } } //取消選擇鍵selectionKey iterator.remove(); } }
客戶端
//1.獲取通道 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8080)); //2.設定為非堵塞模式 socketChannel.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); //3.傳送資料給服務端 //控制檯輸入資料 Scanner scanner = new Scanner(System.in); while (scanner.hasNext()){ String msg = scanner.next(); buf.put(msg.getBytes()); buf.flip(); socketChannel.write(buf); buf.clear(); } //4.關閉連線 socketChannel.close();