【轉載】Java NIO學習 & NIO BIO AIO 比較

能夠參考這個頁面:html

http://www.iteye.com/magazines/132-Java-NIO (下面這個頁面也有)java

http://ifeve.com/overview/編程

 

另,在這篇文章裏面,寫了個NIO示例程序: http://www.cnblogs.com/charlesblc/p/6074057.html後端

 

Java NIO 由如下幾個核心部分組成: 

數組

  • Channels
  • Buffers
  • Selectors

雖然Java NIO 中除此以外還有不少類和組件,但在我看來,Channel,Buffer 和 Selector 構成了核心的API。其它組件,如Pipe和FileLock,只不過是與三個核心組件共同使用的工具類。緩存

 

基本上,全部的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 數據能夠從Channel讀到Buffer中,也能夠從Buffer 寫到Channel中。服務器

下面是JAVA NIO中的一些主要Channel的實現: 

網絡

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

正如你所看到的,這些通道涵蓋了UDP 和 TCP 網絡IO,以及文件IO。 上面我寫的代碼裏,就用到了SocketChannel和ServerSocketChannel.多線程

 

如下是Java NIO裏關鍵的Buffer實現: 

架構

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

這些Buffer覆蓋了你能經過IO發送的基本數據類型:byte, short, int, long, float, double 和 char。 

Java NIO 還有個 Mappedyteuffer,用於表示內存映射文件。

 

Selector 

Selector容許單線程處理多個 Channel。若是你的應用打開了多個鏈接(通道),但每一個鏈接的流量都很低,使用Selector就會很方便。例如,在一個聊天服務器中。 

這是在一個單線程中使用一個Selector處理3個Channel的圖示: 

要使用Selector,得向Selector註冊Channel,而後調用它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,線程就能夠處理這些事件,事件的例子有如新鏈接進來,數據接收等。 

 

應該什麼時候使用IO,什麼時候使用NIO呢?

 

IO NIO
Stream oriented Buffer oriented
Blocking IO Non blocking IO
  Selectors

 

面向流與面向緩衝 

Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。此外,它不能先後移動流中的數據。若是須要先後移動從流中讀取的數據,須要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不一樣。數據讀取到一個它稍後處理的緩衝區,須要時可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。可是,還須要檢查是否該緩衝區中包含全部您須要處理的數據。並且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏還沒有處理的數據。 

阻塞與非阻塞IO 

Java IO的各類流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,因此直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。 線程一般將非阻塞IO的空閒時間用於在其它通道上執行IO操做,因此一個單獨的線程如今能夠管理多個輸入和輸出通道(channel)。 

選擇器(Selectors) 

Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,你能夠註冊多個通道使用一個選擇器,而後使用一個單獨的線程來「選擇」通道:這些通道里已經有能夠處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。 

 

總結 

NIO可以讓您只使用一個(或幾個)單線程管理多個通道(網絡鏈接或文件),但付出的代價是解析數據可能會比從一個阻塞流中讀取數據更復雜。 

若是須要管理同時打開的成千上萬個鏈接,這些鏈接每次只是發送少許的數據,例如聊天服務器,實現NIO的服務器多是一個優點。一樣,若是你須要維持許多打開的鏈接到其餘計算機上,如P2P網絡中,使用一個單獨的線程來管理你全部出站鏈接,多是一個優點。

 

基本的 Channel 示例 

下面是一個使用FileChannel讀取數據到Buffer中的示例: 

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");  
FileChannel inChannel = aFile.getChannel();  
  
ByteBuffer buf = ByteBuffer.allocate(48);  
  
int bytesRead = inChannel.read(buf);  
while (bytesRead != -1) {  
  
System.out.println("Read " + bytesRead);  
buf.flip();  
  
while(buf.hasRemaining()){  
System.out.print((char) buf.get());  
}  
  
buf.clear();  
bytesRead = inChannel.read(buf);  
}  
aFile.close();  

注意 buf.flip() 的調用,首先讀取數據到Buffer,而後反轉Buffer,接着再從Buffer中讀取數據。下一節會深刻講解Buffer的更多細節。 

 

Buffer的基本用法 

使用Buffer讀寫數據通常遵循如下四個步驟: 

    • 寫入數據到Buffer
    • 調用flip()方法
    • 從Buffer中讀取數據
    • 調用clear()方法或者compact()方法

clear()方法會清空整個緩衝區。compact()方法只會清除已經讀過的數據。任何未讀的數據都被移到緩衝區的起始處,新寫入的數據將放到緩衝區未讀數據的後面。

 

爲了理解Buffer的工做原理,須要熟悉它的三個屬性: 

    • capacity
    • position
    • limit

 

capacity 

做爲一個內存塊,Buffer有一個固定的大小值,也叫「capacity」.你只能往裏寫capacity個byte、long,char等類型。一旦Buffer滿了,須要將其清空(經過讀數據或者清除數據)才能繼續寫數據往裏寫數據。 

position 

當你寫數據到Buffer中時,position表示當前的位置。初始的position值爲0.當一個byte、long等數據寫到Buffer後, position會向前移動到下一個可插入數據的Buffer單元。position最大可爲capacity – 1。 

當讀取數據時,也是從某個特定位置讀。當將Buffer從寫模式切換到讀模式,position會被重置爲0。當從Buffer的position處讀取數據時,position向前移動到下一個可讀的位置。 

limit 

在寫模式下,Buffer的limit表示你最多能往Buffer裏寫多少數據,其實就是等於Buffer的capacity。 

當切換Buffer到讀模式時, limit表示你最多能讀到多少數據。所以,當切換Buffer到讀模式時,limit會被設置成寫模式下的position值。換句話說,你能讀到以前寫入的全部數據(limit被設置成已寫數據的數量,這個值在寫模式下就是position) 

 

Buffer的類型 

Java NIO 有如下Buffer類型: 

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer


如你所見,這些Buffer類型表明了不一樣的數據類型。換句話說,就是能夠經過char,short,int,long,float 或 double類型來操做緩衝區中的字節。 

MappedByteBuffer 有些特別,在涉及它的專門章節中再講。 

 

Buffer的分配 

要想得到一個Buffer對象首先要進行分配。 每個Buffer類都有一個allocate方法。下面是一個分配48字節capacity的ByteBuffer的例子。 

ByteBuffer buf = ByteBuffer.allocate(48);

 

向Buffer中寫數據 

寫數據到Buffer有兩種方式: 

  • 從Channel寫到Buffer。
  • 經過Buffer的put()方法寫到Buffer裏。


從Channel寫到Buffer的例子 

int bytesRead = inChannel.read(buf); //read into buffer.

經過put方法寫Buffer的例子: 

buf.put(127);

put方法有不少版本,容許你以不一樣的方式把數據寫入到Buffer中。例如, 寫到一個指定的位置,或者把一個字節數組寫入到Buffer。 更多Buffer實現的細節參考JavaDoc。 

flip()方法 

flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,並將limit設置成以前position的值。 

換句話說,position如今用於標記讀的位置,limit表示以前寫進了多少個byte、char等 —— 如今能讀取多少個byte、char等。 

 

從Buffer中讀取數據 

從Buffer中讀取數據有兩種方式: 

  • 從Buffer讀取數據到Channel。
  • 使用get()方法從Buffer中讀取數據。


從Buffer讀取數據到Channel的例子: 

//read from buffer into channel.  
int bytesWritten = inChannel.write(buf); 

使用get()方法從Buffer中讀取數據的例子 

byte aByte = buf.get();  

get方法有不少版本,容許你以不一樣的方式從Buffer中讀取數據。例如,從指定position讀取,或者從Buffer中讀取數據到字節數組。更多Buffer實現的細節參考JavaDoc。 

rewind()方法 

Buffer.rewind()將position設回0,因此你能夠重讀Buffer中的全部數據。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。 

clear()與compact()方法 

一旦讀完Buffer中的數據,須要讓Buffer準備好再次被寫入。能夠經過clear()或compact()方法來完成。 

若是調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數據並未清除,只是這些標記告訴咱們能夠從哪裏開始往Buffer裏寫數據。 

若是Buffer中有一些未讀的數據,調用clear()方法,數據將「被遺忘」,意味着再也不有任何標記會告訴你哪些數據被讀過,哪些尚未。 

若是Buffer中仍有未讀的數據,且後續還須要這些數據,可是此時想要先先寫些數據,那麼使用compact()方法。

compact()方法將全部未讀的數據拷貝到Buffer起始處。而後將position設到最後一個未讀元素正後面。limit屬性依然像clear()方法同樣,設置成capacity。如今Buffer準備好寫數據了,可是不會覆蓋未讀的數據。

 

mark()與reset()方法 

經過調用Buffer.mark()方法,能夠標記Buffer中的一個特定position。以後能夠經過調用Buffer.reset()方法恢復到這個position。例如:

buffer.mark();

//call buffer.get() a couple of times, e.g. during parsing.

buffer.reset();  //set position back to mark.

equals()與compareTo()方法 

可使用equals()和compareTo()方法兩個Buffer。 

equals() 

當知足下列條件時,表示兩個Buffer相等: 

  • 有相同的類型(byte、char、int等)。
  • Buffer中剩餘的byte、char等的個數相等。
  • Buffer中全部剩餘的byte、char等都相同。


如你所見,equals只是比較Buffer的一部分,不是每個在它裏面的元素都比較。實際上,它只比較Buffer中的剩餘元素。 

compareTo()方法 

compareTo()方法比較兩個Buffer的剩餘元素(byte、char等), 若是知足下列條件,則認爲一個Buffer「小於」另外一個Buffer: 

    • 第一個不相等的元素小於另外一個Buffer中對應的元素。
    • 全部元素都相等,但第一個Buffer比另外一個先耗盡(第一個Buffer的元素個數比另外一個少)。

注:剩餘元素是從 position到limit之間的元素

 

 

分散(Scatter)/彙集(Gather)

Java NIO開始支持scatter/gather,scatter/gather用於描述從Channel(譯者注:Channel在中文常常翻譯爲通道)中讀取或者寫入到Channel的操做。 

分散(scatter)從Channel中讀取是指在讀操做時將讀取的數據寫入多個buffer中。所以,Channel將從Channel中讀取的數據「分散(scatter)」到多個Buffer中。 

彙集(gather)寫入Channel是指在寫操做時將多個buffer的數據寫入同一個Channel,所以,Channel 將多個Buffer中的數據「彙集(gather)」後發送到Channel。 

scatter / gather常常用於須要將傳輸的數據分開處理的場合,例如傳輸一個由消息頭和消息體組成的消息,你可能會將消息體和消息頭分散到不一樣的buffer中,這樣你能夠方便的處理消息頭和消息體。 

Scattering Reads 

Scattering Reads是指數據從一個channel讀取到多個buffer中。代碼示例以下: 

ByteBuffer header = ByteBuffer.allocate(128);  
ByteBuffer body   = ByteBuffer.allocate(1024);  
  
ByteBuffer[] bufferArray = { header, body };  
  
channel.read(bufferArray);  

當一個buffer被寫滿後,channel緊接着向另外一個buffer中寫。 

Scattering Reads在移動下一個buffer前,必須填滿當前的buffer,這也意味着它不適用於動態消息(譯者注:消息大小不固定)。換句話說,若是存在消息頭和消息體,消息頭必須完成填充(例如 128byte),Scattering Reads才能正常工做。 

 

Gathering Writes 

Gathering Writes是指數據從多個buffer寫入到同一個channel。

代碼示例以下: 

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

//write data into buffers

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

 

buffers數組是write()方法的入參,write()方法會按照buffer在數組中的順序,將數據寫入到channel,注意只有position和limit之間的數據纔會被寫入。所以,若是一個buffer的容量爲128byte,可是僅僅包含58byte的數據,那麼這58byte的數據將被寫入到channel中。所以與Scattering Reads相反,Gathering Writes能較好的處理動態消息。 

 

通道之間的數據傳輸

在Java NIO中,若是兩個通道中有一個是FileChannel,那你能夠直接將數據從一個channel(譯者注:channel中文常譯做通道)傳輸到另一個channel。 

transferFrom() 

FileChannel的transferFrom()方法能夠將數據從源通道傳輸到FileChannel中(譯者注:這個方法在JDK文檔中的解釋爲將字節從給定的可讀取字節通道傳輸到此通道的文件中)。下面是一個簡單的例子:

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();

long position = 0;
long count = fromChannel.size();

toChannel.transferFrom(position, count, fromChannel);

另有transferTo,略。

 

選擇器(Selector)

Selector(選擇器)是Java NIO中可以檢測一到多個NIO通道,並可以知曉通道是否爲諸如讀寫事件作好準備的組件。這樣,一個單獨的線程能夠管理多個channel,從而管理多個網絡鏈接。 

(1)  爲何使用Selector? 

僅用單個線程來處理多個Channels的好處是,只須要更少的線程來處理通道。事實上,能夠只用一個線程處理全部的通道。對於操做系統來講,線程之間上下文切換的開銷很大,並且每一個線程都要佔用系統的一些資源(如內存)。所以,使用的線程越少越好。 

可是,須要記住,現代的操做系統和CPU在多任務方面表現的愈來愈好,因此多線程的開銷隨着時間的推移,變得愈來愈小了。實際上,若是一個CPU有多個內核,不使用多任務多是在浪費CPU能力。無論怎麼說,關於那種設計的討論應該放在另外一篇不一樣的文章中。在這裏,只要知道使用Selector可以處理多個通道就足夠了。 

代碼示例:

Selector selector = Selector.open(); 

channel.configureBlocking(false);  
SelectionKey key = channel.register(selector,  
    Selectionkey.OP_READ);  

Channel必須處於非阻塞模式下。這意味着不能將FileChannel與Selector一塊兒使用,
由於FileChannel不能切換到非阻塞模式。而套接字通道均可以。

當向Selector註冊Channel時,register()方法會返回一個SelectionKey對象。這個對象包含了一些你感興趣的屬性: 

    • interest集合
    • ready集合
    • Channel
    • Selector
    • 附加的對象(可選)

 

(5)  經過Selector選擇通道 

下面是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()方法調用之間,只有一個通道就緒了。 

 

(6)  wakeUp() 

某個線程調用select()方法後阻塞了,即便沒有通道已經就緒,也有辦法讓其從select()方法返回。只要讓其它線程在第一個線程調用select()方法的那個對象上調用Selector.wakeup()方法便可。阻塞在select()方法上的線程會立馬返回。 

若是有其它線程調用了wakeup()方法,但當前沒有線程阻塞在select()方法上,下個調用select()方法的線程會當即「醒來(wake up)」。 

(7)  close() 

用完Selector後調用其close()方法會關閉該Selector,且使註冊到該Selector上的全部SelectionKey實例無效。通道自己並不會關閉。 

 

完整示例

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set selectedKeys = selector.selectedKeys();
  Iterator keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove</a>();
  }
}

 

文件通道

Java NIO中的FileChannel是一個鏈接到文件的通道。能夠經過文件通道讀寫文件。 

FileChannel沒法設置爲非阻塞模式,它老是運行在阻塞模式下。 

在使用FileChannel以前,必須先打開它。可是,咱們沒法直接打開一個FileChannel,須要經過使用一個InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例。

 

FileChannel的force方法 

FileChannel.force()方法將通道里還沒有寫入磁盤的數據強制寫到磁盤上。出於性能方面的考慮,操做系統會將數據緩存在內存中,因此沒法保證寫入到FileChannel裏的數據必定會即時寫到磁盤上。要保證這一點,須要調用force()方法。 

force()方法有一個boolean類型的參數,指明是否同時將文件元數據(權限信息等)寫到磁盤上。 

 

Socket 通道

能夠經過如下2種方式建立SocketChannel: 

    • 打開一個SocketChannel並鏈接到互聯網上的某臺服務器。
    • 一個新鏈接到達ServerSocketChannel時,會建立一個SocketChannel。

管道(Pipe)

Java NIO 管道是2個線程之間的單向數據鏈接。Pipe有一個source通道和一個sink通道。數據會被寫到sink通道,從source通道讀取。 

這裏是Pipe原理的圖示: 

 

AIO   參考(link

  • Java BIO : 同步並阻塞,服務器實現模式爲一個鏈接一個線程,即客戶端有鏈接請求時服務器端就須要啓動一個線程進行處理,若是這個鏈接不作任何事情會形成沒必要要的線程開銷,固然能夠經過線程池機制改善。

  • Java NIO : 同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。Java AIO(NIO.2) : 異步非阻塞,服務器實現模式爲一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理.

  • Java AIO(NIO.2) : 異步非阻塞,服務器實現模式爲一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理.

適用場景分析:

  • BIO方式適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4之前的惟一選擇,但程序直觀簡單易理解。

  • NIO方式適用於鏈接數目多且鏈接比較短(輕操做)的架構,好比聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。

  • AIO方式使用於鏈接數目多且鏈接比較長(重操做)的架構,好比相冊服務器,充分調用OS參與併發操做,編程比較複雜,JDK7開始支持。

 

如下參考(link

NIO的最重要的地方是當一個鏈接建立後,不須要對應一個線程,這個鏈接會被註冊到多路複用器上面,因此全部的鏈接只須要一個線程就能夠搞定,當這個線程 中的多路複用器進行輪詢的時候,發現鏈接上有請求的話,纔開啓一個線程進行處理,也就是一個請求一個線程模式。

 

AIO:  

    HTTP/1.1出現後,有了Http長鏈接,這樣除了超時和指明特定關閉的http header外,這個連接是一直打開的狀態的,這樣在NIO處理中能夠進一步的進化,在後端資源中能夠實現資源池或者隊列,當請求來的話,開啓的線程把請 求和請求數據傳送給後端資源池或者隊列裏面就返回,而且在全局的地方保持住這個現場(哪一個鏈接的哪一個請求等),這樣前面的線程仍是能夠去接受其餘的請求, 然後端的應用的處理只須要執行隊列裏面的就能夠了,這樣請求處理和後端應用是異步的.當後端處理完,到全局地方獲得現場,產生響應,這個就實現了異步處 理。

 

     BIO是一個鏈接一個線程。

   NIO是一個請求一個線程。

   AIO是一個有效請求一個線程。

 

具體代碼級原理的還能夠參考這兩篇文章:

基於BIO的Java Socket通訊

基於Java NIO的Socket通訊

 

而AIO的示例,能夠參考(link

 

在AIO socket編程中,服務端通道是AsynchronousServerSocketChannel,這個類提供了一個open()靜態工廠,一個bind()方法用於綁定服務端IP地址(還有端口號),另外還提供了accept()用於接收用戶鏈接請求。在客戶端使用的通道是AsynchronousSocketChannel,這個通道處理提供open靜態工廠方法外,還提供了read和write方法。

在AIO編程中,發出一個事件(accept read write等)以後要指定事件處理類(回調函數),AIO中的事件處理類是CompletionHandler<V,A>,這個接口定義了以下兩個方法,分別在異步操做成功和失敗時被回調。

 

   void completed(V result, A attachment);

   void failed(Throwable exc, A attachment);

 

 看起來和我寫的異步http client也很接近。異步client應該也是aio的一種,不過它用了單獨的庫asynchttpclient. 文字連接:

http://www.cnblogs.com/charlesblc/p/6104896.html

 

Java AIO的代碼示例:

服務端:(注意引用的文件也是nio的)

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class AIOEchoServer {

    public final static int PORT = 8001;
    public final static String IP = "127.0.0.1";

    
    private AsynchronousServerSocketChannel server = null;
    
    public AIOEchoServer(){
        try {
            //一樣是利用工廠方法產生一個通道,異步通道 AsynchronousServerSocketChannel
            server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(IP,PORT));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    //使用這個通道(server)來進行客戶端的接收和處理
    public void start(){
        System.out.println("Server listen on "+PORT);
        
        //註冊事件和事件完成後的處理器,這個CompletionHandler就是事件完成後的處理器
        server.accept(null,new CompletionHandler<AsynchronousSocketChannel,Object>(){

            final ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            @Override
            public void completed(AsynchronousSocketChannel result,Object attachment) {
                
                System.out.println(Thread.currentThread().getName());
                Future<Integer> writeResult = null;
                
                try{
                    buffer.clear();
                    result.read(buffer).get(100,TimeUnit.SECONDS);
                    
                    System.out.println("In server: "+ new String(buffer.array()));
                    
                    //將數據寫回客戶端
                    buffer.flip();
                    writeResult = result.write(buffer);
                }catch(InterruptedException | ExecutionException | TimeoutException e){
                    e.printStackTrace();
                }finally{
                    server.accept(null,this);
                    try {
                        writeResult.get();
                        result.close();
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("failed:"+exc);
            }
            
        });
    }
    
    public static void main(String[] args) {
        new AIOEchoServer().start();
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

 

 客戶端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AIOClient {

    public static void main(String[] args) throws IOException {
        
        final AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
        
        InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1",8001);
        
        CompletionHandler<Void, ? super Object> handler = new CompletionHandler<Void,Object>(){

            @Override
            public void completed(Void result, Object attachment) {
                client.write(ByteBuffer.wrap("Hello".getBytes()),null, 
                        new CompletionHandler<Integer,Object>(){

                            @Override
                            public void completed(Integer result,
                                    Object attachment) {
                                final ByteBuffer buffer = ByteBuffer.allocate(1024);
                                client.read(buffer,buffer,new CompletionHandler<Integer,ByteBuffer>(){

                                    @Override
                                    public void completed(Integer result,
                                            ByteBuffer attachment) {
                                        buffer.flip();
                                        System.out.println(new String(buffer.array()));
                                        try {
                                            client.close();
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                    }

                                    @Override
                                    public void failed(Throwable exc,
                                            ByteBuffer attachment) {
                                    }
                                    
                                });
                            }

                            @Override
                            public void failed(Throwable exc, Object attachment) {
                            }
                    
                });
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
            }
            
        };
        
        client.connect(serverAddress, null, handler);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

要實際實驗一下。

注意上面有一個語法 ? super Object,是正確的語法,是什麼意思呢?

若是知道<? extends A>的用法的話,<? super A>的用法就不難理解了。
<? extends A>表示類型必須是A或者A的子類
<? super A>表示類型必須是A或者A的超類

通常用在類型模板裏,那上面? super Object 的意思就是Object 或者Object的父類。

上面直接打印  System.out.println(new String(buffer.array()));

在打印出結果後,還會打印出不少多餘字符。

使用 System.out.println(buffer.toString()); 打印的不是結果,而是:

java.nio.HeapByteBuffer[pos=0 lim=5 cap=1024]

 

最後,我加上了Arrays.copyOfRange

System.out.println("Get Message");
System.out.printf("result is %d \n", result);
byte[] bys = Arrays.copyOfRange(buffer.array(), 0, result);
System.out.println(new String(bys));

就能正常打印了:

Get Message
result is 5 
Hello
Get Message done
相關文章
相關標籤/搜索