1. 基本概念
IO是主存和外部設備(硬盤、終端和網絡等)傳輸數據的過程。IO是操作系統的底層功能實現,底層通過I/O指令進行完成。
2. nio簡介
nio是java New IO的簡稱(並不只是指非阻塞IO),在jdk1.4裏提供的新api。Sun官方標榜的特性如下:– 爲所有的原始類型提供(Buffer)緩存支持。
– 字符集編碼解碼解決方案。
– Channel:一個新的原始I/O抽象。
– 支持鎖和內存映射文件的文件訪問接口。
– 提供多路(non-bloking)非阻塞式的高伸縮性網絡I/O。
詳細介紹可見 http://www.iteye.com/topic/834447
3. 非阻塞 IO
何爲阻塞、何爲非阻塞,非阻塞原理。
何爲阻塞?
一個常見的網絡IO通訊流程如下:
何爲非阻塞?
下面有個隱喻:
一輛從 A 開往 B 的公共汽車上,路上有很多點可能會有人下車。司機不知道哪些點會有哪些人會下車,對於需要下車的人,如何處理更好?
1. 司機過程中定時詢問每個乘客是否到達目的地,若有人說到了,那麼司機停車,乘客下車。 ( 類似阻塞式 )
2. 每個人告訴售票員自己的目的地,然後睡覺,司機只和售票員交互,到了某個點由售票員通知乘客下車。 ( 類似非阻塞 )
很顯然,每個人要到達某個目的地可以認爲是一個線程,司機可以認爲是 CPU 。在阻塞式裏面,每個線程需要不斷的輪詢,上下文切換,以達到找到目的地的結果。而在非阻塞方式裏,每個乘客 ( 線程 ) 都在睡覺 ( 休眠 ) ,只在真正外部環境準備好了才喚醒,這樣的喚醒肯定不會阻塞。
socket的操作都有一個共同的結構:
1. Read request
2. Decode request
3. Process service
4. Encode reply
5. Send reply
經典的網絡服務的設計如下圖,在每個線程中完成對數據的處理:
但這種模式在用戶負載增加時,性能將下降非常的快。我們需要重新尋找一個新的方案,保持數據處理的流暢,很顯然,事件觸發機制是最好的解決辦法,當有事件發生時,會觸動handler,然後開始數據的處理。
Reactor模式類似於AWT中的Event處理:Reactor模式參與者
1.Reactor 負責響應IO事件,一旦發生,廣播發送給相應的Handler去處理,這類似於AWT的thread
2.Handler 是負責非堵塞行爲,類似於AWT ActionListeners;同時負責將handlers與event事件綁定,類似於AWT addActionListener
非阻塞的原理
把整個過程切換成小的任務,通過任務間協作完成。
由一個專門的線程來處理所有的 IO 事件,並負責分發
事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。
線程通訊:線程之間通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的進程切換。
Reactor就是上面隱喻的售票員角色。每個線程的處理流程大概都是讀取數據、解碼、計算處理、編碼、發送響應
如圖:
Java的NIO爲reactor模式提供了實現的基礎機制,它的Selector當發現某個channel有數據時,會通過SlectorKey來告知我們,在此我們實現事件和handler的綁定。
我們來看看Reactor模式代碼:
public class Reactor implements Runnable{
final Selector selector;
final ServerSocketChannel serverSocket;
Reactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),port);
serverSocket.socket().bind(address);
serverSocket.configureBlocking(false);
//向selector註冊該channel
SelectionKey sk =serverSocket.register(selector,SelectionKey.OP_ACCEPT);
logger.debug("-->Start serverSocket.register!");
//利用sk的attache功能綁定Acceptor 如果有事情,觸發Acceptor
sk.attach(new Acceptor());
logger.debug("-->attach(new Acceptor()!");
}
public void run() { // normally in a new Thread
try {
while (!Thread.interrupted())
{
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
//Selector如果發現channel有OP_ACCEPT或READ事件發生,下列遍歷就會進行。
while (it.hasNext())
//來一個事件 第一次觸發一個accepter線程
//以後觸發SocketReadHandler
dispatch((SelectionKey)(it.next()));
selected.clear();
}
}catch (IOException ex) {
logger.debug("reactor stop!"+ex);
}
}
//運行Acceptor或SocketReadHandler
void dispatch(SelectionKey k) {
Runnable r = (Runnable)(k.attachment());
if (r != null){
// r.run();
}
}
class Acceptor implements Runnable { // inner
public void run() {
try {
logger.debug("-->ready for accept!");
SocketChannel c = serverSocket.accept();
if (c != null)
//調用Handler來處理channel
new SocketReadHandler(selector, c);
}
catch(IOException ex) {
logger.debug("accept stop!"+ex);
}
}
}
}
以上代碼中巧妙使用了SocketChannel的attach功能,將Hanlder和可能會發生事件的channel鏈接在一起,當發生事件時,可以立即觸發相應鏈接的Handler。
將數據讀出後,可以將這些數據處理線程做成一個線程池,這樣,數據讀出後,立即扔到線程池中,這樣加速處理速度:
可參考:
http://www.jdon.com/concurrent/reactor.htm
http://javag.iteye.com/blog/221641