一、基本概念描述
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基礎
Buffer和Channel是標準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 讀寫數據一般遵循以下四個步驟:
- 寫入數據到 Buffer蹲嚣;
- 調用 flip() 方法递瑰;
- 從 Buffer 中讀取數據;
- 調用 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中的流彤钟。但是它和流相比還有一些不同:
- Channel是雙向的,既可以讀又可以寫跷叉,而流是單向的
- Channel可以進行異步的讀寫
- 對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猿妈。
因此,如果從文件讀取數據的話巍虫,需要如下三步:
- 從FileInputStream獲取Channel
- 創(chuàng)建Buffer
- 從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三個核心對象中的Buffer和Channel壮莹,現在我們就重點介紹一下第三個核心對象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中的哪些時間感興趣,事件類型有四種:
- Connect
- Accept
- Read
- 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);
}
}