Java NIO的介紹及工作原理
針對傳統(tǒng)I/O 工作模式的不足,NIO 工具包提出了基于Buffer(緩沖區(qū))、Channel(通道)、Selector(選擇器)的新模式;Selector(選擇器)、可選擇的Channel(通道)和SelectionKey(選擇鍵)配合起來使用,可以實現(xiàn)并發(fā)的非阻塞型I/O 能力。
NIO 工具包的成員
Buffer(緩沖器)
Buffer 類是一個抽象類,它有7 個子類分別對應于七種基本的數(shù)據(jù)類型:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和ShortBuffer。每一個Buffer對象相當于一個數(shù)據(jù)容器,可以把它看作內存中的一個大的數(shù)組,用來存儲和提取所有基本類型(boolean 型除外)的數(shù)據(jù)。Buffer 類的核心是一塊內存區(qū),可以直接對其執(zhí)行與內存有關的操作,利用操作系統(tǒng)特性和能力提高和改善Java 傳統(tǒng)I/O 的性能。
Channel(通道)
Channel 被認為是NIO 工具包的一大創(chuàng)新點,是(Buffer)緩沖器和I/O 服務之間的通道,具有雙向性,既可以讀入也可以寫出,可以更高效的傳遞數(shù)據(jù)。我們這里主要討論ServerSocketChannel 和SocketChannel,它們都繼承了SelectableChannel,是可選擇的通道,分別可以工作在同步和異步兩種方式下(這里的可選擇不是指可以選擇兩種工作方式,而是指可以有選擇的注冊自己感興趣的事件)。當通道工作在同步方式時,它的功能和編程方法與傳統(tǒng)的ServerSocket、Socket 對象相似;當通道工作在異步工作方式時,進行輸入輸出處理不必等到輸入輸出完畢才返回,并且可以將其感興趣的(如:接受操作、連接操作、讀出操作、寫入操作)事件注冊到Selector 對象上,與Selector 對象協(xié)同工作可以更有效率的支持和管理并發(fā)的網(wǎng)絡套接字連接。
Selector(選擇器)和SelectionKey(選擇鍵)
各類 Buffer 是數(shù)據(jù)的容器對象;各類Channel 實現(xiàn)在各類Buffer 與各類I/O 服務間傳輸數(shù)據(jù)。Selector 是實現(xiàn)并發(fā)型非阻塞I/O 的核心,各種可選擇的通道將其感興趣的事件注冊到Selector 對象上,Selector 在一個循環(huán)中不斷輪循監(jiān)視這各些注冊在其上的Socket 通道。SelectionKey 類則封裝了SelectableChannel 對象在Selector 中的注冊信息。當Selector 監(jiān)測到在某個注冊的SelectableChannel 上發(fā)生了感興趣的事件時,自動激活產生一個SelectionKey對象,在這個對象中記錄了哪一個SelectableChannel 上發(fā)生了哪種事件,通過對被激活的SelectionKey 的分析,外界可以知道每個SelectableChannel 發(fā)生的具體事件類型,進行相應的處理。
NIO 工作原理
通過上面的討論,我們可以看出在并發(fā)型服務器程序中使用NIO,實際上是通過網(wǎng)絡事件驅動模型實現(xiàn)的。我們應用Select 機制,不用為每一個客戶端連接新啟線程處理,而是將其注冊到特定的Selector 對象上,這就可以在單線程中利用Selector 對象管理大量并發(fā)的網(wǎng)絡連接,更好的利用了系統(tǒng)資源;采用非阻塞I/O 的通信方式,不要求阻塞等待I/O 操作完成即可返回,從而減少了管理I/O 連接導致的系統(tǒng)開銷,大幅度提高了系統(tǒng)性能。
當有讀或寫等任何注冊的事件發(fā)生時,可以從Selector 中獲得相應的SelectionKey , 從SelectionKey 中可以找到發(fā)生的事件和該事件所發(fā)生的具體的SelectableChannel,以獲得客戶端發(fā)送過來的數(shù)據(jù)。由于在非阻塞網(wǎng)絡I/O 中采用了事件觸發(fā)機制,處理程序可以得到系統(tǒng)的主動通知,從而可以實現(xiàn)底層網(wǎng)絡I/O 無阻塞、流暢地讀寫,而不像在原來的阻塞模式下處理程序需要不斷循環(huán)等待。使用NIO,可以編寫出性能更好、更易擴展的并發(fā)型服務器程序。
并發(fā)型服務器程序的實現(xiàn)代碼
應用 NIO 工具包,基于非阻塞網(wǎng)絡I/O 設計的并發(fā)型服務器程序與以往基于阻塞I/O 的實現(xiàn)程序有很大不同,在使用非阻塞網(wǎng)絡I/O 的情況下,程序讀取數(shù)據(jù)和寫入數(shù)據(jù)的時機不是由程序員控制的,而是Selector 決定的。下面便給出基于非阻塞網(wǎng)絡I/O 的并發(fā)型服務器程序的核心代碼片段:
- import java.io.*; //引入Java.io包
 - import java.net.*; //引入Java.net包
 - import java.nio.channels.*; //引入Java.nio.channels包
 - import java.util.*; //引入Java.util包
 - public class TestServer implements Runnable
 - {
 - /**
 - * 服務器Channel對象,負責接受用戶連接
 - */
 - private ServerSocketChannel server;
 - /**
 - * Selector對象,負責監(jiān)控所有的連接到服務器的網(wǎng)絡事件的發(fā)生
 - */
 - private Selector selector;
 - /**
 - * 總的活動連接數(shù)
 - */
 - private int activeSockets;
 - /**
 - * 服務器Channel綁定的端口號
 - */
 - private int port ;
 - /**
 - *
 - * 構造函數(shù)
 - */
 - public TestServer()throws IOException
 - {
 - activeSockets=0;
 - port=9999;//初始化服務器Channel綁定的端口號為9999
 - selector= Selector.open();//初始化Selector對象
 - server=ServerSocketChannel.open();//初始化服務器Channel對象
 - ServerSocket socket=server.socket();//獲取服務器Channel對應的//ServerSocket對象
 - socket.bind(new InetSocketAddress(port));//把Socket綁定到監(jiān)聽端口9999上
 - server.configureBlocking(false);//將服務器Channel設置為非阻塞模式
 - server.register(selector,SelectionKey.OP_ACCEPT);//將服務器Channel注冊到
 - Selector對象,并指出服務器Channel所感興趣的事件為可接受請求操作
 - }
 - public void run()
 - {
 - while(true)
 - {
 - try
 - {
 - /**
 - *應用Select機制輪循是否有用戶感興趣的新的網(wǎng)絡事件發(fā)生,當沒有
 - * 新的網(wǎng)絡事件發(fā)生時,此方法會阻塞,直到有新的網(wǎng)絡事件發(fā)生為止
 - */
 - selector.select();
 - }
 - catch(IOException e)
 - {
 - continue;//當有異常發(fā)生時,繼續(xù)進行循環(huán)操作
 - }
 - /**
 - * 得到活動的網(wǎng)絡連接選擇鍵的集合
 - */
 - Set<SelectionKey> keys=selector.selectedKeys();
 - activeSockets=keys.size();//獲取活動連接的數(shù)目
 - if(activeSockets==0)
 - {
 - continue;//如果連接數(shù)為0,則繼續(xù)進行循環(huán)操作
 - }
 - /**
 - /**
 - * 應用For—Each循環(huán)遍歷整個選擇鍵集合
 - */
 - for(SelectionKey key :keys)
 - {
 - /**
 - * 如果關鍵字狀態(tài)是為可接受,則接受連接,注冊通道,以接受更多的*
 - 事件,進行相關的服務器程序處理
 - */
 - if(key.isAcceptable())
 - {
 - doServerSocketEvent(key);
 - continue;
 - }
 - /**
 - * 如果關鍵字狀態(tài)為可讀,則說明Channel是一個客戶端的連接通道,
 - * 進行相應的讀取客戶端數(shù)據(jù)的操作
 - */
 - if(key.isReadable())
 - {
 - doClientReadEvent(key);
 - continue;
 - }
 - /**
 - * 如果關鍵字狀態(tài)為可寫,則也說明Channel是一個客戶端的連接通道,
 - * 進行相應的向客戶端寫數(shù)據(jù)的操作
 - */
 - if(key.isWritable())
 - {
 - doClinetWriteEvent(key);
 - continue;
 - }
 - }
 - }
 - }
 - /**
 - * 處理服務器事件操作
 - * @param key 服務器選擇鍵對象
 - */
 - private void doServerSocketEvent(SelectionKey key)
 - {
 - SocketChannel client=null;
 - try
 - {
 - ServerSocketChannel server=(ServerSocketChannel)key.channel();
 - client=server.accept();
 - if(client==null)
 - {
 - return;
 - }
 - client.configureBlocking(false);//將客戶端Channel設置為非阻塞型
 - /**
 - /**
 - * 將客戶端Channel注冊到Selector對象上,并且指出客戶端Channel所感
 - * 興趣的事件為可讀和可寫
 - */
 - client.register(selector,SelectionKey.OP_READ|SelectionKey.OP_READ);
 - }catch(IOException e)
 - {
 - try
 - {
 - client.close();
 - }catch(IOException e1){}
 - }
 - }
 - /**
 - * 進行向客戶端寫數(shù)據(jù)操作
 - * @param key 客戶端選擇鍵對象
 - */
 - private void doClinetWriteEvent(SelectionKey key)
 - {
 - 代碼實現(xiàn)略;
 - }
 - /**
 - * 進行讀取客戶短數(shù)據(jù)操作
 - * @param key 客戶端選擇鍵對象
 - */
 - private void doClientReadEvent(SelectionKey key)
 - {
 - 代碼實現(xiàn)略;
 - }
 - }
 
從上面對代碼可以看出,使用非阻塞性I/O進行并發(fā)型服務器程序設計分三個部分:1.向Selector對象注冊感興趣的事件;2.從Selector中獲取所感興趣的事件;3.根據(jù)不同的事件進行相應的處理。
結 語
通過使用NIO 工具包進行并發(fā)型服務器程序設計,一個或者很少幾個Socket 線程就可以處理成千上萬個活動的Socket 連接,大大降低了服務器端程序的開銷;同時網(wǎng)絡I/O 采取非阻塞模式,線程不再在讀或寫時阻塞,操作系統(tǒng)可以更流暢的讀寫數(shù)據(jù)并可以更有效地向CPU 傳遞數(shù)據(jù)進行處理,以便更有效地提高系統(tǒng)的性能。
原文鏈接:http://futureinhands.iteye.com/blog/953576
【編輯推薦】















 
 
 



 
 
 
 