NIO核心之選擇器(Selector)與NIO網路通訊原理
選擇器(Selector)
選擇器(Selector) 是 SelectableChannle 物件的多路複用器,Selector 可以同時監控多個 SelectableChannel 的 IO 狀況,也就是說,利用 Selector可使一個單獨的執行緒管理多個 Channel。Selector 是非阻塞 IO 的核心。
-
-
Selector 能夠檢測多個註冊的通道上是否有事件發生(注意:多個 Channel 以事件的方式可以註冊到同一個Selector,如果有事件發生,便獲取事件然後針對每個事件進行相應的處理。這樣就可以只用一個單執行緒去管理多個通道,也就是管理多個連線和請求。
-
只有在 連線/通道 真正有讀寫事件發生時,才會進行讀寫,就大大地減少了系統開銷,並且不必為每個連線都建立一個執行緒,不用去維護多個執行緒
-
-
選擇 器(Selector)的應用
建立 Selector :通過呼叫 Selector.open() 方法建立一個 Selector。
Selector selector = Selector.open();
向選擇器註冊通道:SelectableChannel.register(Selector sel, int ops)
//1. 獲取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open();//2. 切換非阻塞模式 ssChannel.configureBlocking(false); //3. 繫結連線 ssChannel.bind(new InetSocketAddress(9898)); //4. 獲取選擇器 Selector selector = Selector.open(); //5. 將通道註冊到選擇器上, 並且指定“監聽接收事件” ssChannel.register(selector, SelectionKey.OP_ACCEPT);
當呼叫 register(Selector sel, int ops) 將通道註冊選擇器時,選擇器對通道的監聽事件,需要通過第二個引數 ops 指定。可以監聽的事件型別(用 可使用 SelectionKey 的四個常量 表示):
-
讀 : SelectionKey.OP_READ (1)
-
寫 : SelectionKey.OP_WRITE (4)
-
連線 : SelectionKey.OP_CONNECT (8)
-
接收 : SelectionKey.OP_ACCEPT (16)
若註冊時不止監聽一個事件,則可以使用“位或”操作符連線。
int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE
NIO網路通訊原理
Selector可以實現: 一個 I/O 執行緒可以併發處理 N 個客戶端連線和讀寫操作,這從根本上解決了傳統同步阻塞 I/O 一連線一執行緒模型,架構的效能、彈性伸縮能力和可靠性都得到了極大的提升。
public class Server { public static void main(String[] args) throws Exception { System.out.println("----服務端啟動---"); // 1、獲取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); // 2、切換為非阻塞模式 ssChannel.configureBlocking(false); // 3、繫結連線的埠 ssChannel.bind(new InetSocketAddress(9999)); // 4、獲取選擇器Selector Selector selector = Selector.open(); // 5、將通道都註冊到選擇器上去,並且開始指定監聽接收事件 ssChannel.register(selector , SelectionKey.OP_ACCEPT); // 6、使用Selector選擇器輪詢已經就緒好的事件 while (selector.select() > 0){ System.out.println("開始新一輪事件處理"); // 7、獲取選擇器中的所有註冊的通道中已經就緒好的事件 迭代器的方式獲得:selectedKeys獲取當前選擇器輪詢到的所有事件 iterator()迭代器 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // 8、開始遍歷這些準備好的事件 如果有下一個 就提取 while (it.hasNext()){ // 提取當前這個事件 SelectionKey sk = it.next(); // 9、判斷這個事件具體是什麼 是接入事件?是 則接入客戶端通道 if(sk.isAcceptable()){ // 10、直接獲取當前接入的客戶端通道 SocketChannel schannel = ssChannel.accept(); // 11 、切換成非阻塞模式 schannel.configureBlocking(false); // 12、將本客戶端通道註冊到選擇器 進行讀的監聽(客戶端寫過來 服務端負責讀) schannel.register(selector , SelectionKey.OP_READ); //如果是讀事件 }else if(sk.isReadable()){ // 13、獲取當前選擇器上的讀就緒事件 先獲得通道 獲得客戶端連線 有通道則說明有資料過來 SocketChannel sChannel = (SocketChannel) sk.channel(); // 14、設定buffer讀取資料 ByteBuffer buf = ByteBuffer.allocate(1024); int len = 0; //迴圈讀取通道中的緩衝區 >0 還有資料未讀 迴圈讀完 while((len = sChannel.read(buf)) > 0){ buf.flip();//歸位 System.out.println(new String(buf.array() , 0, len)); buf.clear();// 清除之前的資料 } } it.remove(); // 處理完畢之後需要移除當前事件 } } } }
public class Client { public static void main(String[] args) throws Exception { // 1、獲取通道 SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); // 2、切換成非阻塞模式 sChannel.configureBlocking(false); // 3、分配指定緩衝區大小 ByteBuffer buf = ByteBuffer.allocate(1024); // 4、傳送資料給服務端 Scanner sc = new Scanner(System.in); while (true){ System.out.println("請說:"); String msg = sc.nextLine(); buf.put(("theone:"+msg).getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } } }
總結:
BIO、NIO:
-
Java BIO : 同步並阻塞,伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,當然可以通過執行緒池機制改善。
-
Java NIO : 同步非阻塞,伺服器實現模式為一個請求一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。
BIO、NIO適用場景分析:
-
BIO方式適用於連線數目比較小且固定的架構,這種方式對伺服器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程式直觀簡單易理解。
-
NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜,JDK1.4開始支援。