(轉(zhuǎn)載說明:本文非原創(chuàng),轉(zhuǎn)載自http://ifeve.com/java-nio-all/)
Java NIO: Channels and Buffers(通道和緩沖區(qū))
標(biāo)準(zhǔn)的IO基于字節(jié)流和字符流進(jìn)行操作的,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。
Java NIO: Non-blocking IO(非阻塞IO)
Java NIO可以讓你非阻塞的使用IO,例如:當(dāng)線程從通道讀取數(shù)據(jù)到緩沖區(qū)時(shí),線程還是可以進(jìn)行其他事情。當(dāng)數(shù)據(jù)被寫入到緩沖區(qū)時(shí),線程可以繼續(xù)處理它。從緩沖區(qū)寫入通道也類似。
Java NIO: Selectors(選擇器)
Java NIO引入了選擇器的概念,選擇器用于監(jiān)聽多個(gè)通道的事件(比如:連接打開,數(shù)據(jù)到達(dá))。因此,單個(gè)的線程可以監(jiān)聽多個(gè)數(shù)據(jù)通道。
1 Channel
通道的讀寫是雙向的,可異步的,并且都需要通過Buffer來進(jìn)行。這些是Java NIO中最重要的通道的實(shí)現(xiàn):
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
FileChannel 從文件中讀寫數(shù)據(jù)。DatagramChannel 能通過UDP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)。SocketChannel 能通過TCP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)。ServerSocketChannel可以監(jiān)聽新進(jìn)來的TCP連接,像Web服務(wù)器那樣。對(duì)每一個(gè)新進(jìn)來的連接都會(huì)創(chuàng)建一個(gè)SocketChannel。
2 Buffer
首先看個(gè)例子
RandomAccessFile file = new RandomAccessFile("a.txt","rw");
FileChannel channel = file.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = channel.read(buf);
while(bytesRead!=0){ buf.flip(); while(buf.hasRemaining())
{
System.out.println((char)buf.get());
}
buf.clear();
bytesRead = channel.read(buf);
}
為了理解Buffer的工作原理,需要熟悉它的三個(gè)屬性:
capacity
作為一個(gè)內(nèi)存塊,Buffer有一個(gè)固定的大小值,也叫“capacity”.你只能往里寫capacity個(gè)byte、long,char等類型。一旦Buffer滿了,需要將其清空(通過讀數(shù)據(jù)或者清除數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)往里寫數(shù)據(jù)。
position
當(dāng)你寫數(shù)據(jù)到Buffer中時(shí),position表示當(dāng)前的位置。初始的position值為0.當(dāng)一個(gè)byte、long等數(shù)據(jù)寫到Buffer后, position會(huì)向前移動(dòng)到下一個(gè)可插入數(shù)據(jù)的Buffer單元。position最大可為capacity – 1.當(dāng)讀取數(shù)據(jù)時(shí),也是從某個(gè)特定位置讀。當(dāng)將Buffer從寫模式切換到讀模式,position會(huì)被重置為0. 當(dāng)從Buffer的position處讀取數(shù)據(jù)時(shí),position向前移動(dòng)到下一個(gè)可讀的位置。
limit
在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。 寫模式下,limit等于Buffer的capacity。當(dāng)切換Buffer到讀模式時(shí), limit表示你最多能讀到多少數(shù)據(jù)。因此,當(dāng)切換Buffer到讀模式時(shí),limit會(huì)被設(shè)置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數(shù)據(jù)(limit被設(shè)置成已寫數(shù)據(jù)的數(shù)量,這個(gè)值在寫模式下就是position)
2.1 Buffer的類型
Java NIO 有以下Buffer類型
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
如你所見,這些Buffer類型代表了不同的數(shù)據(jù)類型。換句話說,就是可以通過char,short,int,long,float 或 double類型來操作緩沖區(qū)中的字節(jié)。
2.2 Buffer的分配
要想獲得一個(gè)Buffer對(duì)象首先要進(jìn)行分配。 每一個(gè)Buffer類都有一個(gè)allocate方法。下面是一個(gè)分配48字節(jié)capacity的ByteBuffer的例子。
ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);
2.3向Buffer中寫數(shù)據(jù)
寫數(shù)據(jù)到Buffer有兩種方式:
從Channel寫到Buffer。
通過Buffer的put()方法寫到Buffer里。
從Channel寫到Buffer的例子
int bytesRead = inChannel.read(buf); //read into buffer.
//通過put方法寫B(tài)uffer的例子:
buf.put(127);
put方法有很多版本,允許你以不同的方式把數(shù)據(jù)寫入到Buffer中。例如, 寫到一個(gè)指定的位置,或者把一個(gè)字節(jié)數(shù)組寫入到Buffer。 更多Buffer實(shí)現(xiàn)的細(xì)節(jié)參考JavaDoc。
flip()方法
flip方法將Buffer從寫模式切換到讀模式。調(diào)用flip()方法會(huì)將position設(shè)回0,并將limit設(shè)置成之前position的值。
換句話說,position現(xiàn)在用于標(biāo)記讀的位置,limit表示之前寫進(jìn)了多少個(gè)byte、char等 —— 現(xiàn)在能讀取多少個(gè)byte、char等。
從Buffer中讀取數(shù)據(jù)從Buffer中讀取數(shù)據(jù)有兩種方式:從Buffer讀取數(shù)據(jù)到Channel。使用get()方法從Buffer中讀取數(shù)據(jù)。
2.4從Buffer讀取數(shù)據(jù)到Channel的例子:
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
//使用get()方法從Buffer中讀取數(shù)據(jù)的例子
byte aByte = buf.get();
get方法有很多版本,允許你以不同的方式從Buffer中讀取數(shù)據(jù)。
例如,從指定position讀取,或者從Buffer中讀取數(shù)據(jù)到字節(jié)數(shù)組。更多Buffer實(shí)現(xiàn)的細(xì)節(jié)參考JavaDoc。
rewind()方法
Buffer.rewind()將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變,仍然表示能從Buffer中讀取多少個(gè)元素(byte、char等)。
clear()與compact()方法
一旦讀完Buffer中的數(shù)據(jù),需要讓Buffer準(zhǔn)備好再次被寫入。可以通過clear()或compact()方法來完成。如果調(diào)用的是clear()方法,position將被設(shè)回0,limit被設(shè)置成 capacity的值。換句話說,Buffer 被清空了。Buffer中的數(shù)據(jù)并未清除,只是這些標(biāo)記告訴我們可以從哪里開始往Buffer里寫數(shù)據(jù)。如果Buffer中有一些未讀的數(shù)據(jù),調(diào)用clear()方法,數(shù)據(jù)將“被遺忘”,意味著不再有任何標(biāo)記會(huì)告訴你哪些數(shù)據(jù)被讀過,哪些還沒有。如果Buffer中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù),但是此時(shí)想要先先寫些數(shù)據(jù),那么使用compact()方法。compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設(shè)到最后一個(gè)未讀元素正后面。limit屬性依然像clear()方法一樣,設(shè)置成capacity。現(xiàn)在Buffer準(zhǔn)備好寫數(shù)據(jù)了,但是不會(huì)覆蓋未讀的數(shù)據(jù)。mark()與reset()方法通過調(diào)用Buffer.mark()方法,可以標(biāo)記Buffer中的一個(gè)特定position。之后可以通過調(diào)用Buffer.reset()方法恢復(fù)到這個(gè)position。例如:
buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset();
//set position back to mark.
3 Scatter/Gather
分散(scatter)從Channel中讀取是指在讀操作時(shí)將讀取的數(shù)據(jù)寫入多個(gè)buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個(gè)Buffer中。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
聚集(gather)寫入Channel是指在寫操作時(shí)將多個(gè)buffer的數(shù)據(jù)寫入同一個(gè)Channel,因此,Channel 將多個(gè)Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
4 通道之間的數(shù)據(jù)傳輸
在Java NIO中,如果兩個(gè)通道中有一個(gè)是FileChannel,那你可以直接將數(shù)據(jù)從一個(gè)channel(譯者注:channel中文常譯作通道)傳輸?shù)搅硗庖粋€(gè)channel。
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);
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();
fromChannel.transferTo(position, count, toChannel);
5 Selector
Selector可以在一個(gè)線程中管理多個(gè)通道。
5.1 創(chuàng)建和注冊(cè)
創(chuàng)建
Selector selector = Selector.open();
注冊(cè)
channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
與Selector一起使用時(shí),Channel必須處于非阻塞模式下。這意味著不能將FileChannel與Selector一起使用,因?yàn)镕ileChannel不能切換到非阻塞模式。而套接字通道都可以。
Selector可以監(jiān)聽四種事件,這四種事件用SelectionKey的四個(gè)常量來表示:SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
如果你對(duì)不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來,如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
5.2 SelectionKey
SelectionKey中包含了以下屬性:
interest集合
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
ready集合
ready集合表示通道已經(jīng)準(zhǔn)備就緒的操作集合,可以以如下的方式訪問ready集合:
int readySet = selectionKey.readyOps();
也可以
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel + Selector
從SelectionKey訪問Channel和Selector很簡單。如下:
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
附加的對(duì)象
可以將一個(gè)對(duì)象或者更多信息附著到SelectionKey上,這樣就能方便的識(shí)別某個(gè)給定的通道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集數(shù)據(jù)的某個(gè)對(duì)象。使用方法如下:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
還可以在用register()方法向Selector注冊(cè)Channel的時(shí)候附加對(duì)象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
5.3 通過Selector選擇通道
一旦向Selector注冊(cè)了一或多個(gè)通道,就可以調(diào)用幾個(gè)重載的select()方法。這些方法返回你所感興趣的事件(如連接、接受、讀或?qū)懀┮呀?jīng)準(zhǔn)備就緒的那些通道。換句話說,如果你對(duì)“讀就緒”的通道感興趣,select()方法會(huì)返回讀事件已經(jīng)就緒的那些通道。
下面是select()方法:
int select()
int select(long timeout)
int selectNow()
當(dāng)像Selector注冊(cè)Channel時(shí),Channel.register()方法會(huì)返回一個(gè)SelectionKey 對(duì)象。這個(gè)對(duì)象代表了注冊(cè)到該Selector的通道。可以通過SelectionKey的selectedKeySet()方法訪問這些對(duì)象。
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();
}
這個(gè)循環(huán)遍歷已選擇鍵集中的每個(gè)鍵,并檢測(cè)各個(gè)鍵所對(duì)應(yīng)的通道的就緒事件。
注意每次迭代末尾的keyIterator.remove()調(diào)用。Selector不會(huì)自己從已選擇鍵集中移除SelectionKey實(shí)例。必須在處理完通道時(shí)自己移除。下次該通道變成就緒時(shí),Selector會(huì)再次將其放入已選擇鍵集中。
SelectionKey.channel()方法返回的通道需要轉(zhuǎn)型成你要處理的類型,如ServerSocketChannel或SocketChannel等。
5.4wakeUp()
某個(gè)線程調(diào)用select()方法后阻塞了,即使沒有通道已經(jīng)就緒,也有辦法讓其從select()方法返回。只要讓其它線程在第一個(gè)線程調(diào)用select()方法的那個(gè)對(duì)象上調(diào)用Selector.wakeup()方法即可。阻塞在select()方法上的線程會(huì)立馬返回。
如果有其它線程調(diào)用了wakeup()方法,但當(dāng)前沒有線程阻塞在select()方法上,下個(gè)調(diào)用select()方法的線程會(huì)立即“醒來(wake up)”。
5.5 close()
用完Selector后調(diào)用其close()方法會(huì)關(guān)閉該Selector,且使注冊(cè)到該Selector上的所有SelectionKey實(shí)例無效。通道本身并不會(huì)關(guān)閉。
5.6 完整的示例
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();
}
}
6 FileChannel
Java NIO中的FileChannel是一個(gè)連接到文件的通道。可以通過文件通道讀寫文件。FileChannel無法設(shè)置為非阻塞模式,它總是運(yùn)行在阻塞模式下。
打開FileChannel
在使用FileChannel之前,必須先打開它。但是,我們無法直接打開一個(gè)FileChannel,需要通過使用一個(gè)InputStream、OutputStream或RandomAccessFile來獲取一個(gè)FileChannel實(shí)例。下面是通過RandomAccessFile打開FileChannel的示例:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt","rw");FileChannel inChannel = aFile.getChannel();
從FileChannel讀取數(shù)據(jù)
調(diào)用多個(gè)read()方法之一從FileChannel中讀取數(shù)據(jù)。如:
ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf);
首先,分配一個(gè)Buffer。從FileChannel中讀取的數(shù)據(jù)將被讀到Buffer中。然后,調(diào)用FileChannel.read()方法。該方法將數(shù)據(jù)從FileChannel讀取到Buffer中。read()方法返回的int值表示了有多少字節(jié)被讀到了Buffer中。如果返回-1,表示到了文件末尾。
向FileChannel寫數(shù)據(jù)
使用FileChannel.write()方法向FileChannel寫數(shù)據(jù),該方法的參數(shù)是一個(gè)Buffer。如:
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining())
{
channel.write(buf);
}
注意FileChannel.write()是在while循環(huán)中調(diào)用的。因?yàn)闊o法保證write()方法一次能向FileChannel寫入多少字節(jié),因此需要重復(fù)調(diào)用write()方法,直到Buffer中已經(jīng)沒有尚未寫入通道的字節(jié)。
關(guān)閉FileChannel
用完FileChannel后必須將其關(guān)閉。如:
channel.close();
FileChannel的position方法
有時(shí)可能需要在FileChannel的某個(gè)特定位置進(jìn)行數(shù)據(jù)的讀/寫操作。可以通過調(diào)用position()方法獲取FileChannel的當(dāng)前位置。也可以通過調(diào)用position(long pos)方法設(shè)置FileChannel的當(dāng)前位置。這里有兩個(gè)例子:
long pos = channel.position();
channel.position(pos +123);
如果將位置設(shè)置在文件結(jié)束符之后,然后試圖從文件通道中讀取數(shù)據(jù),讀方法將返回-1 —— 文件結(jié)束標(biāo)志。如果將位置設(shè)置在文件結(jié)束符之后,然后向通道中寫數(shù)據(jù),文件將撐大到當(dāng)前位置并寫入數(shù)據(jù)。這可能導(dǎo)致“文件空洞”,磁盤上物理文件中寫入的數(shù)據(jù)間有空隙。FileChannel的size方法FileChannel實(shí)例的size()方法將返回該實(shí)例所關(guān)聯(lián)文件的大小。如:
long fileSize = channel.size();
FileChannel的truncate方法可以使用FileChannel.truncate()方法截取一個(gè)文件。截取文件時(shí),文件將中指定長度后面的部分將被刪除。如:
channel.truncate(1024);
這個(gè)例子截取文件的前1024個(gè)字節(jié)。FileChannel的force方法FileChannel.force()方法將通道里尚未寫入磁盤的數(shù)據(jù)強(qiáng)制寫到磁盤上。出于性能方面的考慮,操作系統(tǒng)會(huì)將數(shù)據(jù)緩存在內(nèi)存中,所以無法保證寫入到FileChannel里的數(shù)據(jù)一定會(huì)即時(shí)寫到磁盤上。要保證這一點(diǎn),需要調(diào)用force()方法。force()方法有一個(gè)boolean類型的參數(shù),指明是否同時(shí)將文件元數(shù)據(jù)(權(quán)限信息等)寫到磁盤上。下面的例子同時(shí)將文件數(shù)據(jù)和元數(shù)據(jù)強(qiáng)制寫到磁盤上:
channel.force(true);
7 SocketChannel
Java NIO中的SocketChannel是一個(gè)連接到TCP網(wǎng)絡(luò)套接字的通道。可以通過以下2種方式創(chuàng)建SocketChannel:打開一個(gè)SocketChannel并連接到互聯(lián)網(wǎng)上的某臺(tái)服務(wù)器。一個(gè)新連接到達(dá)ServerSocketChannel時(shí),會(huì)創(chuàng)建一個(gè)SocketChannel。
打開 SocketChannel
下面是SocketChannel的打開方式:
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("[http://jenkov.com](http://jenkov.com/)", 80));
關(guān)閉 SocketChannel
當(dāng)用完SocketChannel之后調(diào)用SocketChannel.close()關(guān)閉SocketChannel:
socketChannel.close();
從 SocketChannel 讀取數(shù)據(jù)
要從SocketChannel中讀取數(shù)據(jù),調(diào)用一個(gè)read()的方法之一。以下是例子:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
首先,分配一個(gè)Buffer。從SocketChannel讀取到的數(shù)據(jù)將會(huì)放到這個(gè)Buffer中。然后,調(diào)用SocketChannel.read()。該方法將數(shù)據(jù)從SocketChannel 讀到Buffer中。read()方法返回的int值表示讀了多少字節(jié)進(jìn)Buffer里。如果返回的是-1,表示已經(jīng)讀到了流的末尾(連接關(guān)閉了)。
寫入 SocketChannel
寫數(shù)據(jù)到SocketChannel用的是SocketChannel.write()方法,該方法以一個(gè)Buffer作為參數(shù)。示例如下:
String newData = "New String to write to file..."+ System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining())
{
channel.write(buf);
}
注意SocketChannel.write()方法的調(diào)用是在一個(gè)while循環(huán)中的。Write()方法無法保證能寫多少字節(jié)到SocketChannel。所以,我們重復(fù)調(diào)用write()直到Buffer沒有要寫的字節(jié)為止。
非阻塞模式
可以設(shè)置 SocketChannel 為非阻塞模式(non-blocking mode).設(shè)置之后,就可以在異步模式下調(diào)用connect(), read() 和write()了。
1.connect()
如果SocketChannel在非阻塞模式下,此時(shí)調(diào)用connect(),該方法可能在連接建立之前就返回了。為了確定連接是否建立,可以調(diào)用finishConnect()的方法。像這樣:
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("[http://jenkov.com](http://jenkov.com/)", 80));
while(! socketChannel.finishConnect() )
{
//wait, or do something else...
}
2.write()
非阻塞模式下,write()方法在尚未寫出任何內(nèi)容時(shí)可能就返回了。所以需要在循環(huán)中調(diào)用write()。前面已經(jīng)有例子了,這里就不贅述了。3.read()非阻塞模式下,read()方法在尚未讀取到任何數(shù)據(jù)時(shí)可能就返回了。所以需要關(guān)注它的int返回值,它會(huì)告訴你讀取了多少字節(jié)。
非阻塞模式與選擇器
非阻塞模式與選擇器搭配會(huì)工作的更好,通過將一或多個(gè)SocketChannel注冊(cè)到Selector,可以詢問選擇器哪個(gè)通道已經(jīng)準(zhǔn)備好了讀取,寫入等。Selector與SocketChannel的搭配使用會(huì)在后面詳講。
8 ServerSocketChannel
Java NIO中的 ServerSocketChannel 是一個(gè)可以監(jiān)聽新進(jìn)來的TCP連接的通道, 就像標(biāo)準(zhǔn)IO中的ServerSocket一樣。ServerSocketChannel類在 java.nio.channels包中。
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true)
{ SocketChannel socketChannel = serverSocketChannel.accept();
//do something with socketChannel...
}
非阻塞模式
ServerSocketChannel可以設(shè)置成非阻塞模式。在非阻塞模式下,accept() 方法會(huì)立刻返回,如果還沒有新進(jìn)來的連接,返回的將是null。 因此,需要檢查返回的SocketChannel是否是null.如:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null)
{
//do something with socketChannel...
}
}
9 DatagramChannel
Java NIO中的DatagramChannel是一個(gè)能收發(fā)UDP包的通道。因?yàn)閁DP是無連接的網(wǎng)絡(luò)協(xié)議,所以不能像其它通道那樣讀取和寫入。它發(fā)送和接收的是數(shù)據(jù)包。
打開 DatagramChannel
下面是 DatagramChannel 的打開方式:
DatagramChannel channel =DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
這個(gè)例子打開的 DatagramChannel可以在UDP端口9999上接收數(shù)據(jù)包。
接收數(shù)據(jù)通過receive()方法從DatagramChannel接收數(shù)據(jù),如:
ByteBuffer buf =ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);
receive()方法會(huì)將接收到的數(shù)據(jù)包內(nèi)容復(fù)制到指定的Buffer. 如果Buffer容不下收到的數(shù)據(jù),多出的數(shù)據(jù)將被丟棄。
發(fā)送數(shù)據(jù)
通過send()方法從DatagramChannel發(fā)送數(shù)據(jù),如:
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
這個(gè)例子發(fā)送一串字符到”jenkov.com”服務(wù)器的UDP端口80。 因?yàn)榉?wù)端并沒有監(jiān)控這個(gè)端口,所以什么也不會(huì)發(fā)生。也不會(huì)通知你發(fā)出的數(shù)據(jù)包是否已收到,因?yàn)閁DP在數(shù)據(jù)傳送方面沒有任何保證。
連接到特定的地址
可以將DatagramChannel“連接”到網(wǎng)絡(luò)中的特定地址的。由于UDP是無連接的,連接到特定地址并不會(huì)像TCP通道那樣創(chuàng)建一個(gè)真正的連接。而是鎖住DatagramChannel ,讓其只能從特定地址收發(fā)數(shù)據(jù)。這里有個(gè)例子:
channel.connect(new InetSocketAddress("jenkov.com", 80));
當(dāng)連接后,也可以使用read()和write()方法,就像在用傳統(tǒng)的通道一樣。只是在數(shù)據(jù)傳送方面沒有任何保證。這里有幾個(gè)例子:
int bytesRead = channel.read(buf);int bytesWritten = channel.write(but);
10 Pipe
Java NIO 管道是2個(gè)線程之間的單向數(shù)據(jù)連接。Pipe有一個(gè)source通道和一個(gè)sink通道。數(shù)據(jù)會(huì)被寫到sink通道,從source通道讀取。這里是Pipe原理的圖示:

創(chuàng)建管道通過Pipe.open()方法打開管道。例如:
Pipe pipe = Pipe.open();
向管道寫數(shù)據(jù)
要向管道寫數(shù)據(jù),需要訪問sink通道。像這樣:
Pipe.SinkChannel sinkChannel = pipe.sink();
通過調(diào)用SinkChannel的write()方法,將數(shù)據(jù)寫入SinkChannel,像這樣:
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining())
{
sinkChannel.write(buf);
}
從管道讀取數(shù)據(jù)
從讀取管道的數(shù)據(jù),需要訪問source通道,像這樣:
Pipe.SourceChannel sourceChannel = pipe.source();
調(diào)用source通道的read()方法來讀取數(shù)據(jù),像這樣:
ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = sourceChannel.read(buf);
read()方法返回的int值會(huì)告訴我們多少字節(jié)被讀進(jìn)了緩沖區(qū)。