Java NIO使用及原理之--選擇器Seclector

Java NIO使用及原理之--選擇器Seclectorjava

本文轉載自:李會軍•寧靜致遠,供本身學習使用服務器

在上一篇文章中介紹了關於緩衝區的一些細節內容,如今終於能夠進入NIO中最有意思的部分非阻塞I/O。一般在進行同步I/O操做時,若是讀取數據,代碼會阻塞直至有 可供讀取的數據。一樣,寫入調用將會阻塞直至數據可以寫入。傳統的Server/Client模式會基於TPR(Thread per Request),服務器會爲每一個客戶端請求創建一個線程,由該線程單獨負責處理一個客戶請求。這種模式帶來的一個問題就是線程數量的劇增,大量的線程會增大服務器的開銷。大多數的實現爲了不這個問題,都採用了線程池模型,並設置線程池線程的最大數量,這由帶來了新的問題,若是線程池中有200個線程,而有200個用戶都在進行大文件下載,會致使第201個用戶的請求沒法及時處理,即使第201個用戶只想請求一個幾KB大小的頁面。傳統的 Server/Client模式以下圖所示:socket

NIO中非阻塞I/O採用了基於Reactor反應器模式(或者說觀察者模式)的工做方式,I/O調用不會被阻塞,相反是註冊感興趣的特定I/O事件,如可讀數據到達,新的套接字鏈接等等,在發生特定事件時,系統再通知咱們。NIO中實現非阻塞I/O的核心對象就是Selector,Selector就是註冊各類I/O事件地 方,並且當那些事件發生時,就是這個對象告訴咱們所發生的事件,以下圖所示:post

從圖中能夠看出,當有讀或寫等任何註冊的事件發生時,能夠從Selector中得到相應的SelectionKey,同時從 SelectionKey中能夠找到發生的事件和該事件所發生的具體的SelectableChannel,以得到客戶端發送過來的數據。學習

使用NIO中非阻塞I/O編寫服務器處理程序,大致上能夠分爲下面三個步驟:spa

1. 向Selector對象註冊感興趣的事件 
2. 從Selector中獲取感興趣的事件 
3. 根據不一樣的事件進行相應的處理線程

接下來咱們用一個簡單的示例來講明整個過程。首先是向Selector對象註冊感興趣的事件:code

 1 /*
 2  * 註冊事件
 3  * */
 4 protected Selector getSelector() throws IOException {
 5     // 建立Selector對象
 6     Selector sel = Selector.open();
 7     
 8     // 建立可選擇通道,並配置爲非阻塞模式
 9     ServerSocketChannel server = ServerSocketChannel.open();
10     server.configureBlocking(false);
11     
12     // 綁定通道到指定端口
13     ServerSocket socket = server.socket();
14     InetSocketAddress address = new InetSocketAddress(port);
15     socket.bind(address);
16     
17     // 向Selector中註冊感興趣的事件
18     server.register(sel, SelectionKey.OP_ACCEPT); 
19     return sel;
20 }

建立了ServerSocketChannel對象,並調用configureBlocking()方法,配置爲非阻塞模式,接下來的三行代碼把該通道綁定到指定端口,最後向Selector中註冊事件,此處指定的是參數是OP_ACCEPT,即指定咱們想要監聽accept事件,也就是新的鏈接發 生時所產生的事件,對於ServerSocketChannel通道來講,咱們惟一能夠指定的參數就是OP_ACCEPT。server

從Selector中獲取感興趣的事件,即開始監聽,進入內部循環:對象

 1 /*
 2  * 開始監聽
 3  * */ 
 4 public void listen() { 
 5     System.out.println("listen on " + port);
 6     try { 
 7         while(true) { 
 8             // 該調用會阻塞,直到至少有一個事件發生
 9             selector.select(); 
10             Set<SelectionKey> keys = selector.selectedKeys();
11             Iterator<SelectionKey> iter = keys.iterator();
12             while (iter.hasNext()) { 
13                 SelectionKey key = (SelectionKey) iter.next(); 
14                 iter.remove(); 
15                 process(key); 
16             } 
17         } 
18     } catch (IOException e) { 
19         e.printStackTrace();
20     } 
21 }

在非阻塞I/O中,內部循環模式基本都是遵循這種方式。首先調用select()方法,該方法會阻塞,直到至少有一個事件發生,而後再使用selectedKeys()方法獲取發生事件的SelectionKey,再使用迭代器進行循環。

最後一步就是根據不一樣的事件,編寫相應的處理代碼:

 1 /*
 2  * 根據不一樣的事件作處理
 3  * */
 4 protected void process(SelectionKey key) throws IOException{
 5     // 接收請求
 6     if (key.isAcceptable()) {
 7         ServerSocketChannel server = (ServerSocketChannel) key.channel();
 8         SocketChannel channel = server.accept();
 9         channel.configureBlocking(false);
10         channel.register(selector, SelectionKey.OP_READ);
11     }
12     // 讀信息
13     else if (key.isReadable()) {
14         SocketChannel channel = (SocketChannel) key.channel(); 
15         int count = channel.read(buffer); 
16         if (count > 0) { 
17             buffer.flip(); 
18             CharBuffer charBuffer = decoder.decode(buffer); 
19             name = charBuffer.toString(); 
20             SelectionKey sKey = channel.register(selector, SelectionKey.OP_WRITE); 
21             sKey.attach(name); 
22         } else { 
23             channel.close(); 
24         } 
25         buffer.clear(); 
26     }
27     // 寫事件
28     else if (key.isWritable()) {
29         SocketChannel channel = (SocketChannel) key.channel(); 
30         String name = (String) key.attachment(); 
31         
32         ByteBuffer block = encoder.encode(CharBuffer.wrap("Hello " + name)); 
33         if(block != null)
34         {
35             channel.write(block);
36         }
37         else
38         {
39             channel.close();
40         }
41  
42      }
43 }

此處分別判斷是接受請求、讀數據仍是寫事件,分別做不一樣的處理。

相關文章
相關標籤/搜索