NIO

一、基本概念描述

1.1 I/O簡介

I/O即輸入輸出,是計算機與外界世界的一個借口既棺。IO操作的實際主題是操作系統(tǒng)。在java編程中懒叛,一般使用流的方式來處理IO丸冕,所有的IO都被視作是單個字節(jié)的移動,通過stream對象一次移動一個字節(jié)薛窥。流IO負責把對象轉換為字節(jié)胖烛,然后再轉換為對象。

關于Java IO相關知識請參考我的另一篇文章:Java IO 詳解

1.2 什么是NIO

NIO即New IO拆檬,這個庫是在JDK1.4中才引入的洪己。NIO和IO有相同的作用和目的,但實現方式不同竟贯,NIO主要用到的是塊答捕,所以NIO的效率要比IO高很多。

在Java API中提供了兩套NIO屑那,一套是針對標準輸入輸出NIO拱镐,另一套就是網絡編程NIO艘款,本篇文章重點介紹標NIO,關于網絡編程NIO請見Java NIO詳解(二)沃琅。

1.3 流與塊的比較

NIO和IO最大的區(qū)別是數據打包和傳輸方式哗咆。IO是以的方式處理數據,而NIO是以的方式處理數據益眉。

面向流的IO一次一個字節(jié)的處理數據晌柬,一個輸入流產生一個字節(jié),一個輸出流就消費一個字節(jié)郭脂。為流式數據創(chuàng)建過濾器就變得非常容易年碘,鏈接幾個過濾器,以便對數據進行處理非常方便而簡單展鸡,但是面向流的IO通常處理的很慢屿衅。

面向塊的IO系統(tǒng)以塊的形式處理數據。每一個操作都在一步中產生或消費一個數據塊莹弊。按塊要比按流快的多涤久,但面向塊的IO缺少了面向流IO所具有的有雅興和簡單性凌简。

二剿配、NIO基礎

BufferChannel是標準NIO中的核心對象(網絡NIO中還有個Selector核心對象,具體請參考Java NIO詳解(二))渗勘,幾乎每一個IO操作中都會用到它們剧罩。

Channel是對原IO中流的模擬栓拜,任何來源和目的數據都必須通過一個Channel對象。一個Buffer實質上是一個容器對象惠昔,發(fā)給Channel的所有對象都必須先放到Buffer中幕与;同樣的,從Channel中讀取的任何數據都要讀到Buffer中镇防。

2.1 關于Buffer

Buffer是一個對象啦鸣,它包含一些要寫入或讀出的數據。在NIO中来氧,數據是放入buffer對象的诫给,而在IO中,數據是直接寫入或者讀到Stream對象的啦扬。應用程序不能直接對 Channel 進行讀寫操作中狂,而必須通過 Buffer 來進行,即 Channel 是通過 Buffer 來讀寫數據的扑毡。

在NIO中胃榕,所有的數據都是用Buffer處理的,它是NIO讀寫數據的中轉池瞄摊。Buffer實質上是一個數組勋又,通常是一個字節(jié)數據苦掘,但也可以是其他類型的數組。但一個緩沖區(qū)不僅僅是一個數組楔壤,重要的是它提供了對數據的結構化訪問鹤啡,而且還可以跟蹤系統(tǒng)的讀寫進程。

使用 Buffer 讀寫數據一般遵循以下四個步驟:

  1. 寫入數據到 Buffer蹲嚣;
  2. 調用 flip() 方法递瑰;
  3. 從 Buffer 中讀取數據;
  4. 調用 clear() 方法或者 compact() 方法隙畜。

當向 Buffer 寫入數據時泣矛,Buffer 會記錄下寫了多少數據。一旦要讀取數據禾蚕,需要通過 flip() 方法將 Buffer

從寫模式切換到讀模式。在讀模式下狂丝,可以讀取之前寫入到 Buffer 的所有數據换淆。

一旦讀完了所有的數據,就需要清空緩沖區(qū)几颜,讓它可以再次被寫入倍试。有兩種方式能清空緩沖區(qū):調用 clear() 或 compact() 方法。clear() 方法會清空整個緩沖區(qū)蛋哭。compact() 方法只會清除已經讀過的數據县习。任何未讀的數據都被移到緩沖區(qū)的起始處,新寫入的數據將放到緩沖區(qū)未讀數據的后面谆趾。

Buffer主要有如下幾種:

[圖片上傳失敗...(image-3bdd2a-1514216408916)]

2.3 關于Channel

Channel是一個對象躁愿,可以通過它讀取和寫入數據』ε睿可以把它看做IO中的流彤钟。但是它和流相比還有一些不同:

  1. Channel是雙向的,既可以讀又可以寫跷叉,而流是單向的
  2. Channel可以進行異步的讀寫
  3. 對Channel的讀寫必須通過buffer對象

正如上面提到的逸雹,所有數據都通過Buffer對象處理,所以云挟,您永遠不會將字節(jié)直接寫入到Channel中梆砸,相反,您是將數據寫入到Buffer中园欣;同樣帖世,您也不會從Channel中讀取字節(jié),而是將數據從Channel讀入Buffer俊庇,再從Buffer獲取這個字節(jié)狮暑。

因為Channel是雙向的鸡挠,所以Channel可以比流更好地反映出底層操作系統(tǒng)的真實情況。特別是在Unix模型中搬男,底層操作系統(tǒng)通常都是雙向的拣展。

這里寫圖片描述

在Java NIO中Channel主要有如下幾種類型:

  • FileChannel:從文件讀取數據的
  • DatagramChannel:讀寫UDP網絡協(xié)議數據
  • SocketChannel:讀寫TCP網絡協(xié)議數據
  • ServerSocketChannel:可以監(jiān)聽TCP連接

三、從理論到實踐:NIO中的讀和寫

IO中的讀和寫缔逛,對應的是數據和Stream备埃,NIO中的讀和寫,則對應的就是通道和緩沖區(qū)褐奴。NIO中從通道中讀劝唇拧:創(chuàng)建一個緩沖區(qū),然后讓通道讀取數據到緩沖區(qū)敦冬。NIO寫入數據到通道:創(chuàng)建一個緩沖區(qū)辅搬,用數據填充它,然后讓通道用這些數據來執(zhí)行寫入脖旱。

3.1 從文件中讀取

我們已經知道堪遂,在NIO系統(tǒng)中,任何時候執(zhí)行一個讀操作萌庆,您都是從Channel中讀取溶褪,而您不是直接從Channel中讀取數據,因為所有的數據都必須用Buffer來封裝践险,所以您應該是從Channel讀取數據到Buffer猿妈。

因此,如果從文件讀取數據的話巍虫,需要如下三步:

  1. 從FileInputStream獲取Channel
  2. 創(chuàng)建Buffer
  3. 從Channel讀取數據到Buffer

下面我們看一下具體過程:

第一步:獲取通道

FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();  

第二步:創(chuàng)建緩沖區(qū)

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

第三步:將數據從通道讀到緩沖區(qū)

fc.read( buffer );

3.2 寫入數據到文件

類似于從文件讀數據彭则,

第一步:獲取一個通道

FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();

第二步:創(chuàng)建緩沖區(qū),將數據放入緩沖區(qū)

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

for (int i=0; i<message.length; ++i) {
 buffer.put( message[i] );
}
buffer.flip();

第三步:把緩沖區(qū)數據寫入通道中

fc.write( buffer );

3.3 讀寫結合

CopyFile是一個非常好的讀寫結合的例子垫言,我們將通過CopyFile這個實力讓大家體會NIO的操作過程贰剥。CopyFile執(zhí)行三個基本的操作:創(chuàng)建一個Buffer,然后從源文件讀取數據到緩沖區(qū)筷频,然后再將緩沖區(qū)寫入目標文件蚌成。

/**
 * 用java NIO api拷貝文件
 * @param src
 * @param dst
 * @throws IOException
 */
public static void copyFileUseNIO(String src,String dst) throws IOException{
    //聲明源文件和目標文件
            FileInputStream fi=new FileInputStream(new File(src));
            FileOutputStream fo=new FileOutputStream(new File(dst));
            //獲得傳輸通道channel
            FileChannel inChannel=fi.getChannel();
            FileChannel outChannel=fo.getChannel();
            //獲得容器buffer
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            while(true){
                //判斷是否讀完文件
                int eof =inChannel.read(buffer);
                if(eof==-1){
                    break;  
                }
                //重設一下buffer的position=0,limit=position
                buffer.flip();
                //開始寫
                outChannel.write(buffer);
                //寫完要重置buffer凛捏,重設position=0,limit=capacity
                buffer.clear();
            }
            inChannel.close();
            outChannel.close();
            fi.close();
            fo.close();
}     

四担忧、需要注意的點

上面程序中有三個地方需要注意

4.1 檢查狀態(tài)

當沒有更多的數據時,拷貝就算完成坯癣,此時 read() 方法會返回 -1 瓶盛,我們可以根據這個方法判斷是否讀完。

int r= fcin.read( buffer );
if (r==-1) {
     break;
     }

4.2 Buffer類的flip、clear方法

控制buffer狀態(tài)的三個變量

  • position:跟蹤已經寫了多少數據或讀了多少數據惩猫,它指向的是下一個字節(jié)來自哪個位置
  • limit:代表還有多少數據可以取出或還有多少空間可以寫入芝硬,它的值小于等于capacity。
  • capacity:代表緩沖區(qū)的最大容量轧房,一般新建一個緩沖區(qū)的時候拌阴,limit的值和capacity的值默認是相等的。

flip奶镶、clear這兩個方法便是用來設置這些值的迟赃。

flip方法

我們先看一下flip的源碼:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
 }

這里寫圖片描述

在上面的FileCopy程序中,寫入數據之前我們調用了buffer.flip();方法厂镇,這個方法把當前的指針位置position設置成了limit纤壁,再將當前指針position指向數據的最開始端,我們現在可以將數據從緩沖區(qū)寫入通道了捺信。 position 被設置為 0酌媒,這意味著我們得到的下一個字節(jié)是第一個字節(jié)。 limit 已被設置為原來的 position迄靠,這意味著它包括以前讀到的所有字節(jié)馍佑,并且一個字節(jié)也不多。

clear方法

先看一下clear的源碼:

 public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

這里寫圖片描述

在上面的FileCopy程序中梨水,寫入數據之后也就是讀數據之前,我們調用了

buffer.clear();方法茵臭,這個方法重設緩沖區(qū)以便接收更多的字節(jié)疫诽。上圖顯示了在調用 clear() 后緩沖區(qū)的狀態(tài)。

轉載請說明出處旦委,原文鏈接:http://blog.csdn.net/suifeng3051/article/details/48160753
在我的上一篇文章JavaNIO詳解(一)中介紹了關于標準輸入輸出NIO相關知識奇徒, 本篇將重點介紹基于網絡編程NIO(異步IO)。

異步IO

異步 I/O 是一種沒有阻塞地讀寫數據的方法缨硝。通常摩钙,在代碼進行

read()

調用時,代碼會阻塞直至有可供讀取的數據查辩。同樣胖笛,

write()調用將會阻塞直至數據能夠寫入,關于同步的IO請參考另一篇文章Java IO宜岛。

另一方面长踊,異步 I/O 調用不但不會阻塞,相反萍倡,您可以注冊對特定 I/O 事件諸如數據可讀身弊、新連接到來等等,而在發(fā)生這樣感興趣的事件時,系統(tǒng)將會告訴您阱佛。

異步 I/O 的一個優(yōu)勢在于帖汞,它允許您同時根據大量的輸入和輸出執(zhí)行 I/O。同步程序常常要求助于輪詢凑术,或者創(chuàng)建許許多多的線程以處理大量的連接翩蘸。使用異步 I/O,您可以監(jiān)聽任何數量的通道上的事件麦萤,不用輪詢鹿鳖,也不用額外的線程。

Selector

在我的JavaNIO詳解(一)中已經詳細介紹了Java NIO三個核心對象中的BufferChannel壮莹,現在我們就重點介紹一下第三個核心對象Selector翅帜。Selector是一個對象,它可以注冊到很多個Channel上命满,監(jiān)聽各個Channel上發(fā)生的事件涝滴,并且能夠根據事件情況決定Channel讀寫。這樣胶台,通過一個線程管理多個Channel歼疮,就可以處理大量網絡連接了。

采用Selector模式的的好處

有了Selector诈唬,我們就可以利用一個線程來處理所有的channels韩脏。線程之間的切換對操作系統(tǒng)來說代價是很高的,并且每個線程也會占用一定的系統(tǒng)資源铸磅。所以赡矢,對系統(tǒng)來說使用的線程越少越好。

但是阅仔,需要記住吹散,現代的操作系統(tǒng)和CPU在多任務方面表現的越來越好,所以多線程的開銷隨著時間的推移八酒,變得越來越小了空民。實際上,如果一個CPU有多個內核羞迷,不使用多任務可能是在浪費CPU能力界轩。不管怎么說,關于那種設計的討論應該放在另一篇不同的文章中衔瓮。在這里耸棒,只要知道使用Selector能夠處理多個通道就足夠了。

下面這幅圖展示了一個線程處理3個 Channel的情況:

這里寫圖片描述

如何創(chuàng)建一個Selector

異步 I/O 中的核心對象名為 Selector报辱。Selector 就是您注冊對各種 I/O 事件興趣的地方与殃,而且當那些事件發(fā)生時单山,就是這個對象告訴您所發(fā)生的事件。

Selector selector = Selector.open();

然后幅疼,就需要注冊Channel到Selector了米奸。

如何注冊Channel到Selector

為了能讓Channel和Selector配合使用,我們需要把Channel注冊到Selector上爽篷。通過調用channel.register()方法來實現注冊:

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

注意悴晰,注冊的Channel

必須設置成異步模式

才可以,,否則異步IO就無法工作逐工,這就意味著我們不能把一個FileChannel注冊到Selector铡溪,因為FileChannel沒有異步模式,但是網絡編程中的SocketChannel是可以的泪喊。

需要注意register()方法的第二個參數棕硫,它是一個“interest set”,意思是注冊的Selector對Channel中的哪些時間感興趣,事件類型有四種:

  1. Connect
  2. Accept
  3. Read
  4. Write

通道觸發(fā)了一個事件意思是該事件已經

Ready(就緒)袒啼。所以哈扮,某個Channel成功連接到另一個服務器稱為

Connect Ready。一個ServerSocketChannel準備好接收新連接稱為

Accept Ready蚓再,一個有數據可讀的通道可以說是

Read Ready滑肉,等待寫數據的通道可以說是Write Ready

上面這四個事件對應到SelectionKey中的四個常量:

1\. SelectionKey.OP_CONNECT
2\. SelectionKey.OP_ACCEPT
3\. SelectionKey.OP_READ
4\. SelectionKey.OP_WRITE

如果你對多個事件感興趣摘仅,可以通過or操作符來連接這些常量:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 

關于SelectionKey

請注意對register()的調用的返回值是一個SelectionKey靶庙。 SelectionKey 代表這個通道在此 Selector 上的這個注冊。當某個 Selector 通知您某個傳入事件時娃属,它是通過提供對應于該事件的 SelectionKey 來進行的惶洲。SelectionKey 還可以用于取消通道的注冊。SelectionKey中包含如下屬性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

Interest Set

就像我們在前面講到的把Channel注冊到Selector來監(jiān)聽感興趣的事件膳犹,interest set就是你要選擇的感興趣的事件的集合。你可以通過SelectionKey對象來讀寫interest set:

int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;   

通過上面例子可以看到签则,我們可以通過用AND 和SelectionKey 中的常量做運算须床,從SelectionKey中找到我們感興趣的事件。

Ready Set

ready set

是通道已經準備就緒的操作的集合渐裂。在一次選Selection之后豺旬,你應該會首先訪問這個ready set。Selection將在下一小節(jié)進行解釋柒凉∽逶模可以這樣訪問ready集合:

int readySet = selectionKey.readyOps();

可以用像檢測interest集合那樣的方法,來檢測Channel中什么事件或操作已經就緒膝捞。但是坦刀,也可以使用以下四個方法,它們都會返回一個布爾類型:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

Channel 和Selector

我們可以通過SelectionKey獲得Selector和注冊的Channel:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector(); 

Attach 一個對象

可以將一個對象或者更多信息attach 到SelectionKey上,這樣就能方便的識別某個給定的通道鲤遥。例如沐寺,可以附加 與通道一起使用的Buffer,或是包含聚集數據的某個對象盖奈。使用方法如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

還可以在用register()方法向Selector注冊Channel的時候附加對象混坞。如:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

通過Selector選擇通道

一旦向Selector注冊了一或多個通道,就可以調用幾個重載的select()方法钢坦。這些方法返回你所感興趣的事件(如連接究孕、接受、讀或寫)已經準備就緒的那些通道爹凹。換句話說厨诸,如果你對“Read Ready”的通道感興趣,select()方法會返回讀事件已經就緒的那些通道:

  • int select(): 阻塞到至少有一個通道在你注冊的事件上就緒
  • int select(long timeout):select()一樣逛万,除了最長會阻塞timeout毫秒(參數)
  • int selectNow(): 不會阻塞泳猬,不管什么通道就緒都立刻返回,此方法執(zhí)行非阻塞的選擇操作宇植。如果自從前一次選擇操作后得封,沒有通道變成可選擇的,則此方法直接返回零指郁。

select()方法返回的int值表示有多少通道已經就緒忙上。亦即,自上次調用select()方法后有多少通道變成就緒狀態(tài)闲坎。如果調用select()方法疫粥,因為有一個通道變成就緒狀態(tài),返回了1腰懂,若再次調用select()方法梗逮,如果另一個通道就緒了,它會再次返回1绣溜。如果對第一個就緒的channel沒有做任何操作慷彤,現在就有兩個就緒的通道,但在每次select()方法調用之間怖喻,只有一個通道處于就緒狀態(tài)底哗。

selectedKeys()

一旦調用了select()方法,它就會返回一個數值锚沸,表示一個或多個通道已經就緒跋选,然后你就可以通過調用selector.selectedKeys()方法返回的SelectionKey集合來獲得就緒的Channel。請看演示方法:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

當你通過Selector注冊一個Channel時哗蜈,channel.register()方法會返回一個SelectionKey對象前标,這個對象就代表了你注冊的Channel坠韩。這些對象可以通過selectedKeys()方法獲得。你可以通過迭代這些selected key來獲得就緒的Channel候生,下面是演示代碼:

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> 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();
}

這個循環(huán)遍歷selected key的集合中的每個key同眯,并對每個key做測試來判斷哪個Channel已經就緒。

請注意循環(huán)中最后的keyIterator.remove()方法唯鸭。Selector對象并不會從自己的selected key集合中自動移除SelectionKey實例须蜗。我們需要在處理完一個Channel的時候自己去移除。當下一次Channel就緒的時候目溉,Selector會再次把它添加到selected key集合中明肮。

SelectionKey.channel()方法返回的Channel需要轉換成你具體要處理的類型,比如是ServerSocketChannel或者SocketChannel等等缭付。

WakeUp()和Close()

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

如果有其它線程調用了wakeup()方法绣檬,但當前沒有線程阻塞在select()方法上足陨,下個調用select()方法的線程會立即“醒來(wake up)”

當用完Selector后調應道掉用close()方法,它將關閉Selector并且使注冊到該Selector上的所有SelectionKey實例無效娇未。通道本身并不會關閉墨缘。

一個完整的例子

下面通過一個MultiPortEcho的例子來演示一下上面整個過程。

public class MultiPortEcho {
 private int ports[];
 private ByteBuffer echoBuffer = ByteBuffer.allocate(1024);
 public MultiPortEcho(int ports[]) throws IOException {
      this.ports = ports;
      go();
 }
 private void go() throws IOException {
      // 1\. 創(chuàng)建一個selector零抬,select是NIO中的核心對象
      // 它用來監(jiān)聽各種感興趣的IO事件
      Selector selector = Selector.open();
      // 為每個端口打開一個監(jiān)聽, 并把這些監(jiān)聽注冊到selector中
      for (int i = 0; i < ports.length; ++i) {
           //2\. 打開一個ServerSocketChannel
           //其實我們沒監(jiān)聽一個端口就需要一個channel
           ServerSocketChannel ssc = ServerSocketChannel.open();
           ssc.configureBlocking(false);//設置為非阻塞
           ServerSocket ss = ssc.socket();
           InetSocketAddress address = new InetSocketAddress(ports[i]);
           ss.bind(address);//監(jiān)聽一個端口
           //3\. 注冊到selector
           //register的第一個參數永遠都是selector
           //第二個參數是我們要監(jiān)聽的事件
           //OP_ACCEPT是新建立連接的事件
           //也是適用于ServerSocketChannel的唯一事件類型
           SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
           System.out.println("Going to listen on " + ports[i]);
      }
      //4\. 開始循環(huán)镊讼,我們已經注冊了一些IO興趣事件
      while (true) {
           //這個方法會阻塞,直到至少有一個已注冊的事件發(fā)生平夜。當一個或者更多的事件發(fā)生時
           // select() 方法將返回所發(fā)生的事件的數量蝶棋。
           int num = selector.select();
           //返回發(fā)生了事件的 SelectionKey 對象的一個 集合
           Set selectedKeys = selector.selectedKeys();
           //我們通過迭代 SelectionKeys 并依次處理每個 SelectionKey 來處理事件
           //對于每一個 SelectionKey,您必須確定發(fā)生的是什么 I/O 事件忽妒,以及這個事件影響哪些 I/O 對象玩裙。
           Iterator it = selectedKeys.iterator();
           while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                //5\. 監(jiān)聽新連接。程序執(zhí)行到這里锰扶,我們僅注冊了 ServerSocketChannel
                //并且僅注冊它們“接收”事件。為確認這一點
                //我們對 SelectionKey 調用 readyOps() 方法寝受,并檢查發(fā)生了什么類型的事件
                if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
                     //6\. 接收了一個新連接坷牛。因為我們知道這個服務器套接字上有一個傳入連接在等待
                     //所以可以安全地接受它;也就是說很澄,不用擔心 accept() 操作會阻塞
                     ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                     SocketChannel sc = ssc.accept();
                     sc.configureBlocking(false);
                     // 7\. 講新連接注冊到selector京闰。將新連接的 SocketChannel 配置為非阻塞的
                     //而且由于接受這個連接的目的是為了讀取來自套接字的數據颜及,所以我們還必須將 SocketChannel 注冊到 Selector上
                     SelectionKey newKey = sc.register(selector,SelectionKey.OP_READ);
                     it.remove();
                     System.out.println("Got connection from " + sc);
                } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                     // Read the data
                     SocketChannel sc = (SocketChannel) key.channel();
                     // Echo data
                     int bytesEchoed = 0;
                     while (true) {
                          echoBuffer.clear();
                          int r = sc.read(echoBuffer);
                          if (r <= 0) {
                               break;
                          }
                          echoBuffer.flip();
                          sc.write(echoBuffer);
                          bytesEchoed += r;
                     }
                     System.out.println("Echoed " + bytesEchoed + " from " + sc);
                     it.remove();
                }
           }
           // System.out.println( "going to clear" );
           // selectedKeys.clear();
           // System.out.println( "cleared" );
      }
 }
 static public void main(String args2[]) throws Exception {
      String args[]={"9001","9002","9003"};
      if (args.length <= 0) {
           System.err.println("Usage: java MultiPortEcho port [port port ...]");
           System.exit(1);
      }
      int ports[] = new int[args.length];
      for (int i = 0; i < args.length; ++i) {
           ports[i] = Integer.parseInt(args[i]);
      }
      new MultiPortEcho(ports);
 }
 }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蹂楣,隨后出現的幾起案子俏站,更是在濱河造成了極大的恐慌,老刑警劉巖痊土,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肄扎,死亡現場離奇詭異,居然都是意外死亡赁酝,警方通過查閱死者的電腦和手機犯祠,發(fā)現死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酌呆,“玉大人衡载,你說我怎么就攤上這事∠对” “怎么了痰娱?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長菩收。 經常有香客問我梨睁,道長,這世上最難降的妖魔是什么坛梁? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任而姐,我火速辦了婚禮,結果婚禮上划咐,老公的妹妹穿的比我還像新娘拴念。我一直安慰自己,他們只是感情好褐缠,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布政鼠。 她就那樣靜靜地躺著,像睡著了一般队魏。 火紅的嫁衣襯著肌膚如雪公般。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天胡桨,我揣著相機與錄音官帘,去河邊找鬼。 笑死昧谊,一個胖子當著我的面吹牛刽虹,可吹牛的內容都是我干的。 我是一名探鬼主播呢诬,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涌哲,長吁一口氣:“原來是場噩夢啊……” “哼胖缤!你這毒婦竟也來了?” 一聲冷哼從身側響起阀圾,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哪廓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后初烘,有當地人在樹林里發(fā)現了一具尸體涡真,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年账月,在試婚紗的時候發(fā)現自己被綠了综膀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡局齿,死狀恐怖剧劝,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情抓歼,我是刑警寧澤讥此,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站谣妻,受9級特大地震影響萄喳,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蹋半,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一他巨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧减江,春花似錦染突、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巡莹,卻和暖如春司志,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背降宅。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工骂远, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腰根。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓激才,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贸营,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java I...
    JackChen1024閱讀 7,555評論 1 143
  • # Java NIO # Java NIO屬于非阻塞IO岩睁,這是與傳統(tǒng)IO最本質的區(qū)別钞脂。傳統(tǒng)IO包括socket和文...
    Teddy_b閱讀 595評論 0 0
  • NIO概述 Java NIO全稱為Non-blocking IO或者New IO,從名字我們知道NIO是非阻塞的I...
    zhong0316閱讀 599評論 0 7
  • 轉自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,306評論 0 22
  • IO流(同步阎毅、阻塞) 、 NIO(同步点弯、非阻塞) 扇调、 NIO2(異步、非阻塞) 概述在我們學習Java的IO流之前...
    _情緒瘋子閱讀 298評論 0 1