摘要:通道是和選擇器一起被注冊(cè)的,并且使用選擇器來(lái)更新通道的就緒狀態(tài)。注冊(cè)不會(huì)立即被取消,但鍵會(huì)立即失效。這個(gè)集合的每個(gè)成員都是相關(guān)的通道被選擇器在前一個(gè)選擇操作中判斷為已經(jīng)準(zhǔn)備好的,并且包
Java NIO是一個(gè)用來(lái)替代標(biāo)準(zhǔn)Java IO API的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會(huì)經(jīng)常存在他的身影。其比傳統(tǒng)的IO更加高效,非阻塞,異步,雙向
NIO主體結(jié)構(gòu)Java NIO的主要構(gòu)成核心就是Buffer、Channel和Selector這三個(gè)
對(duì)于Channel我想要提醒的是,Channel中的數(shù)據(jù)總是要先讀到一個(gè)Buffer,或者總是要從一個(gè)Buffer中寫入
使用Selector,得向Selector注冊(cè)Channel,然后調(diào)用它的select()方法。這個(gè)方法會(huì)一直阻塞到某個(gè)注冊(cè)的通道有事件就緒。一旦這個(gè)方法返回,線程就可以處理這些事件
Channel所有的 IO 在NIO 中都從一個(gè)Channel 開(kāi)始。Channel 有點(diǎn)象流
Channel的實(shí)現(xiàn)FileChannel:從文件中讀寫數(shù)據(jù)
DatagramChannel:通過(guò)UDP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)
SocketChannel:通過(guò)TCP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)
ServerSocketChannel:監(jiān)聽(tīng)新進(jìn)來(lái)的TCP連接,像Web服務(wù)器那樣。對(duì)每一個(gè)新進(jìn)來(lái)的連接都會(huì)創(chuàng)建一個(gè)SocketChannel
Scatter/Gather分散(scatter)從Channel中讀取是指在讀操作時(shí)將讀取的數(shù)據(jù)寫入多個(gè)buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個(gè)Buffer中
聚集(gather)寫入Channel是指在寫操作時(shí)將多個(gè)buffer的數(shù)據(jù)寫入同一個(gè)Channel,因此,Channel 將多個(gè)Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel
通過(guò)這樣的方式可以方便數(shù)據(jù)的讀取,當(dāng)你想要獲取整個(gè)數(shù)據(jù)的一部分的時(shí)候,通過(guò)這種方式可以很快的獲取數(shù)據(jù)
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024); ByteBuffer[] bufferArray = { header, body }; channel.read(bufferArray);
read()方法按照buffer在數(shù)組中的順序?qū)腸hannel中讀取的數(shù)據(jù)寫入到buffer,當(dāng)一個(gè)buffer被寫滿后,channel緊接著向另一個(gè)buffer中寫
transferFrom、transferTo實(shí)現(xiàn)兩個(gè)Channel之間相互連接,數(shù)據(jù)傳遞
public static void trainforNio() { RandomAccessFile fromFile=null; RandomAccessFile toFile=null; try { fromFile = new RandomAccessFile("src/nio.txt", "rw"); // channel獲取數(shù)據(jù) FileChannel fromChannel = fromFile.getChannel(); toFile = new RandomAccessFile("src/toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); System.out.println(toChannel.size()); //position處開(kāi)始向目標(biāo)文件寫入數(shù)據(jù),這里是toChannel long position = toChannel.size(); long count = fromChannel.size(); toChannel.transferFrom(fromChannel, position, count); System.out.println(toChannel.size()); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fromFile != null) { fromFile.close(); } if (toFile != null) { toFile.close(); } } catch (IOException e) { e.printStackTrace(); } } }
transferFrom、transferTo作用是一樣的,只是一個(gè)是tochannal調(diào)用,一個(gè)是fromchannnal調(diào)用
在實(shí)際的運(yùn)用中可能存在源通道的剩余空間小于 count 個(gè)字節(jié),則所傳輸?shù)淖止?jié)數(shù)要小于請(qǐng)求的字節(jié)數(shù)
在SoketChannel的實(shí)現(xiàn)中,SocketChannel只會(huì)傳輸此刻準(zhǔn)備好的數(shù)據(jù)(可能不足count字節(jié))。因此,SocketChannel可能不會(huì)將請(qǐng)求的所有數(shù)據(jù)(count個(gè)字節(jié))全部傳輸?shù)紽ileChannel中
看官一定要仔細(xì)看我栗子中的注釋
BufferBuffer是一個(gè)緩存區(qū),其會(huì)將Channel中的數(shù)據(jù)存儲(chǔ)起來(lái)
Buffer的實(shí)現(xiàn)ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
MappedByteBuffer
capacity,position,limit在講解該主題之前,首先要明白讀模式和寫模式,無(wú)論是Channel還是Buffer都存在這兩種模式,要理解這兩種模式,第一步要明確主題是哪一個(gè),是Channel還是Buffer。舉個(gè)栗子,主角是Channel,讀模式的含義就是從Buffer中獲取數(shù)據(jù),寫模式就是將數(shù)據(jù)寫入Buffer,對(duì)于Buffer則是相反。搞清楚這一點(diǎn),理解下面的就要相對(duì)清楚一點(diǎn)
capacity:作為一個(gè)內(nèi)存塊,其就代表了當(dāng)前Buffer能最多暫存多少數(shù)據(jù)量,存儲(chǔ)的數(shù)據(jù)類型則是根據(jù)上面的Buffer對(duì)象類型,一旦Buffer滿了,需要將其清空(通過(guò)讀數(shù)據(jù)或者清除數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)往里寫數(shù)據(jù)
position:代表當(dāng)前數(shù)據(jù)讀或?qū)懱幱谀莻€(gè)位置。讀模式:被重置從0開(kāi)始,最大值可能為capacity-1或者limit-1,寫模式:被重置從0開(kāi)始,最大值為limit-1
limit:最多能往Buffer里寫多少數(shù)據(jù),limit大小跟數(shù)據(jù)量大小和capacity有關(guān),讀模式:數(shù)據(jù)量>capacity時(shí),limit=capacity,數(shù)據(jù)量=capacity時(shí),limit=capacity,數(shù)據(jù)量
import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class Method { public static void nio() { RandomAccessFile aFile = null; try { aFile = new RandomAccessFile("src/nio.txt", "rw"); // channel獲取數(shù)據(jù) FileChannel fileChannel = aFile.getChannel(); // 初始化Buffer,設(shè)定Buffer每次可以存儲(chǔ)數(shù)據(jù)量 // 創(chuàng)建的Buffer是1024byte的,如果實(shí)際數(shù)據(jù)本身就小于1024,那么limit就是實(shí)際數(shù)據(jù)大小 ByteBuffer buf = ByteBuffer.allocate(1024); // channel中的數(shù)據(jù)寫入Buffer int bytesRead = fileChannel.read(buf); System.out.println(bytesRead); while (bytesRead != -1) { // Buffer切換為讀取模式 buf.flip(); // 讀取數(shù)據(jù) while (buf.hasRemaining()) { System.out.print((char) buf.get()); } // 清空Buffer區(qū) buf.compact(); // 繼續(xù)將數(shù)據(jù)寫入緩存區(qū) bytesRead = fileChannel.read(buf); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (aFile != null) { aFile.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { Method.nio();Buffer讀寫數(shù)據(jù)步驟
寫入數(shù)據(jù)到Buffer(fileChannel.read(buf))
調(diào)用flip()方法(buf.flip())
從Buffer中讀取數(shù)據(jù)(buf.get())
調(diào)用clear()方法或者compact()方法(buf.compact())
Buffer方法
flip():將Buffer讀模式切換到寫模式,并且將position制為0
clear():清空整個(gè)緩沖區(qū)
compact():只會(huì)清除已經(jīng)讀過(guò)的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面
allocate(1024):初始化Buffer,設(shè)定的值就決定capacity值的大小
rewind():將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變,仍然表示能從Buffer中讀取多少個(gè)元素(byte、char等)
mark()與reset():通過(guò)調(diào)用Buffer.mark()方法,可以標(biāo)記Buffer中的一個(gè)特定position。之后可以通過(guò)調(diào)用Buffer.reset()方法恢復(fù)到這個(gè)position
equals():當(dāng)滿足下面三個(gè)條件時(shí),兩個(gè)Buffer才是相等
有相同的類型(byte、char、int等)
Buffer中剩余的byte、char等的個(gè)數(shù)相等
Buffer中所有剩余的byte、char等都相同
只比較的是剩余的數(shù)據(jù)
compareTo():滿足下列條件,則認(rèn)為一個(gè)Buffer“小于”另一個(gè)Buffer
第一個(gè)不相等的元素小于另一個(gè)Buffer中對(duì)應(yīng)的元素
所有元素都相等,但第一個(gè)Buffer比另一個(gè)先耗盡(第一個(gè)Buffer的元素個(gè)數(shù)比另一個(gè)少)
SelectorSelector允許單線程處理多個(gè) Channel。如果你的應(yīng)用打開(kāi)了多個(gè)連接(通道),但每個(gè)連接的流量都很低,使用Selector就會(huì)很方便
大致流程當(dāng)您調(diào)用一個(gè)選擇器對(duì)象的 select( )方法時(shí),相關(guān)的鍵會(huì)被更新,用來(lái)檢查所有被注冊(cè)到該選擇器的通道。您可以獲取一個(gè)鍵的集合,從而找到當(dāng)時(shí)已經(jīng)就緒的通道。通過(guò)遍歷這些鍵,您可以選擇出每個(gè)從上次您調(diào)用 select( )開(kāi)始直到現(xiàn)在,已經(jīng)就緒的通道
選擇器(Selector)的特點(diǎn)public abstract class Selector { // This is a partial API listing public static Selector open( ) throws IOException public abstract boolean isOpen( );//判斷是open public abstract void close( ) throws IOException;//選擇鍵設(shè)置無(wú)效 public abstract SelectionProvider provider( ); }
選擇器類管理著一個(gè)被注冊(cè)的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊(cè)
的,并且使用選擇器來(lái)更新通道的就緒狀態(tài)。當(dāng)這么做的時(shí)候,可以選擇將被激發(fā)的線程掛起,直
到有就緒的的通道
不能注冊(cè)已經(jīng)關(guān)閉的selectableChannel
通過(guò)調(diào)用一個(gè)自定義的 SelectorProvider對(duì)象的 openSelector( )方法來(lái)創(chuàng)建一個(gè) Selector 實(shí)例也是可行的。您可以通過(guò)調(diào)用 provider( )方法來(lái)決定由哪個(gè) SelectorProvider 對(duì)象來(lái)創(chuàng)建給定的 Selector 實(shí)例
通道(Channel)的特點(diǎn)public abstract class SelectableChannel extends AbstractChannel implements Channel { // This is a partial API listing public abstract SelectionKey register (Selector sel, int ops) throws ClosedChannelException; public abstract SelectionKey register (Selector sel, int ops, Object att) throws ClosedChannelException; public abstract boolean isRegistered( ); public abstract SelectionKey keyFor (Selector sel); public abstract int validOps( ); }
繼承SelectableChannel
一個(gè)channel可以注冊(cè)到多個(gè)selector中
一個(gè)selector中同一個(gè)channel只能有一個(gè)
通道被注冊(cè)前,要非阻塞模式
支持Connect、Accept、Read、Write四種可選擇操作事件,但并不是所有的SelectableChannel都存在以上四類,可以通過(guò)validOps()獲取可以使用的操作事件集合
如果你對(duì)不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來(lái)
任何一個(gè)通道和選擇器的注冊(cè)關(guān)系都被封裝在一個(gè) SelectionKey 對(duì)象中。 keyFor( )方法將
返回與該通道和指定的選擇器相關(guān)的鍵。如果通道被注冊(cè)到指定的選擇器上,那么相關(guān)的鍵將被返
回。如果它們之間沒(méi)有注冊(cè)關(guān)系,那么將返回 null
選擇鍵(SelectionKey)的特點(diǎn)package java.nio.channels; public abstract class SelectionKey { public static final int OP_READ public static final int OP_WRITE public static final int OP_CONNECT public static final int OP_ACCEPT public abstract SelectableChannel channel( ); public abstract Selector selector( ); public abstract void cancel( ); public abstract boolean isValid( ); public abstract int interestOps( ); public abstract void interestOps (int ops); public abstract int readyOps( ); public final boolean isReadable( ) public final boolean isWritable( ) public final boolean isConnectable( ) public final boolean isAcceptable( ) public final Object attach (Object ob) public final Object attachment( ) }
封裝了特定的通道與特定的選擇器的注冊(cè)關(guān)系
一個(gè) SelectionKey 對(duì)象包含兩個(gè)以整數(shù)形式進(jìn)行編碼的byte掩碼:一個(gè)用于指示那些通道/
選擇器組合體所關(guān)心的操作(instrest 集合),另一個(gè)表示通道準(zhǔn)備好要執(zhí)行的操作( ready 集合)
當(dāng)終結(jié)注冊(cè)關(guān)系時(shí)
當(dāng)應(yīng)該終結(jié)這種關(guān)系的時(shí)候,可以調(diào)用 SelectionKey對(duì)象的 cancel( )方法??梢酝ㄟ^(guò)調(diào)用 isValid( )方法來(lái)檢查它是否仍然表示一種有效的關(guān)系。當(dāng)鍵被取消時(shí),它將被放在相關(guān)的選擇器的已取消的鍵的集合里。注冊(cè)不會(huì)立即被取消,但鍵會(huì)立即失效。當(dāng)再次調(diào)用 select( )方法時(shí)(或者一個(gè)正在進(jìn)行的 select()調(diào)用結(jié)束時(shí)),已取消的鍵的集合中的被取消的鍵將被清理掉,并且相應(yīng)的注銷也將完成。通道會(huì)被注銷,而新的SelectionKey 將被返回
當(dāng)通道關(guān)閉時(shí)
當(dāng)通道關(guān)閉時(shí),所有相關(guān)的鍵會(huì)自動(dòng)取消(記住,一個(gè)通道可以被注冊(cè)到多個(gè)選擇器上)。當(dāng)
選擇器關(guān)閉時(shí),所有被注冊(cè)到該選擇器的通道都將被注銷,并且相關(guān)的鍵將立即被無(wú)效化(取
消)。一旦鍵被無(wú)效化,調(diào)用它的與選擇相關(guān)的方法就將拋出 CancelledKeyException
interest 集合
當(dāng)前的 interest 集合可以通過(guò)調(diào)用鍵對(duì)象的 interestOps( )方法來(lái)獲取
最初,這應(yīng)該是通道被注冊(cè)時(shí)傳進(jìn)來(lái)的值。這個(gè) interset 集合永遠(yuǎn)不會(huì)被選擇器改變,但您可以通過(guò)調(diào)用 interestOps( )方法并傳入一個(gè)新的byte掩碼參數(shù)來(lái)改變它。 interest 集合也可以通過(guò)將通道注冊(cè)到選擇器上來(lái)改變(實(shí)際上使用一種迂回的方式調(diào)用 interestOps( )),就像 4.1.2 小節(jié)中描的那樣。當(dāng)相關(guān)的 Selector 上的 select( )操作正在進(jìn)行時(shí)改變鍵的 interest 集合,不會(huì)影響那個(gè)正在進(jìn)行的選擇操作。所有更改將會(huì)在 select( )的下一個(gè)調(diào)用中體現(xiàn)出來(lái)
ready集合
可以通過(guò)調(diào)用鍵的 readyOps( )方法來(lái)獲取相關(guān)的通道的已經(jīng)就緒的操作。 ready 集合是 interest
集合的子集,并且表示了 interest 集合中從上次調(diào)用 select( )以來(lái)已經(jīng)就緒的那些操作
SelectionKey 類定義了四個(gè)便于使用的布爾方法來(lái)為您測(cè)試這些byte值: isReadable( ), isWritable( ), isConnectable( ), 和 isAcceptable( )
SelectionKey 對(duì)象包含的 ready 集合與最近一次選擇器對(duì)所注冊(cè)的通道所作的檢查相同。而每個(gè)多帶帶的通道的就緒狀態(tài)會(huì)同時(shí)改變
附加的對(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);
如果選擇鍵的存續(xù)時(shí)間很長(zhǎng),但您附加的對(duì)象不應(yīng)該存在那么長(zhǎng)時(shí)間,請(qǐng)記得在完成后清理附件。否則,您附加的對(duì)象將不能被垃圾回收,您將會(huì)面臨內(nèi)存泄漏問(wèn)題
總體上說(shuō), SelectionKey 對(duì)象是線程安全的,但知道修改 interest 集合的操作是通過(guò) Selector 對(duì)象進(jìn)行同步的是很重要的。這可能會(huì)導(dǎo)致 interestOps( )方法的調(diào)用會(huì)阻塞不確定長(zhǎng)的一段時(shí)間。選擇器所使用的鎖策略(例如是否在整個(gè)選擇過(guò)程中保持這些鎖)是依賴于具體實(shí)現(xiàn)的。幸好,這種多元處理能力被特別地設(shè)計(jì)為可以使用單線程來(lái)管理多個(gè)通道。被多個(gè)線程使用的選擇器也只會(huì)在系統(tǒng)特別復(fù)雜時(shí)產(chǎn)生問(wèn)題。
選擇過(guò)程public abstract class Selector { public abstract Set keys( ); public abstract Set selectedKeys( ); public abstract int select( ) throws IOException; public abstract int select (long timeout) throws IOException; public abstract int selectNow( ) throws IOException; public abstract void wakeup( ); }
已注冊(cè)的鍵的集合
與選擇器關(guān)聯(lián)的已經(jīng)注冊(cè)的鍵的集合。并不是所有注冊(cè)過(guò)的鍵都仍然有效。這個(gè)集合通過(guò)
keys( )方法返回,并且可能是空的。這個(gè)已注冊(cè)的鍵的集合不是可以直接修改的;試圖這么做的話
將引 java.lang.UnsupportedOperationException。
已選擇的鍵的集合
已注冊(cè)的鍵的集合的子集。這個(gè)集合的每個(gè)成員都是相關(guān)的通道被選擇器(在前一個(gè)選擇操作
中)判斷為已經(jīng)準(zhǔn)備好的,并且包含于鍵的 interest 集合中的操作。這個(gè)集合通過(guò) selectedKeys( )方
法返回(并有可能是空的)
不要將已選擇的鍵的集合與 ready 集合弄混了。這是一個(gè)鍵的集合,每個(gè)鍵都關(guān)聯(lián)一個(gè)已經(jīng)準(zhǔn)
備好至少一種操作的通道。每個(gè)鍵都有一個(gè)內(nèi)嵌的 ready 集合,指示了所關(guān)聯(lián)的通道已經(jīng)準(zhǔn)備好的
操作
鍵可以直接從這個(gè)集合中移除,但不能添加
已取消的鍵的集合
已注冊(cè)的鍵的集合的子集,這個(gè)集合包含了 cancel( )方法被調(diào)用過(guò)的鍵(這個(gè)鍵已經(jīng)被無(wú)效
化),但它們還沒(méi)有被注銷。這個(gè)集合是選擇器對(duì)象的私有成員,因而無(wú)法直接訪問(wèn)
在一個(gè)剛初始化的 Selector 對(duì)象中,這三個(gè)集合都是空的。
執(zhí)行步驟
已取消的鍵的集合將會(huì)被檢查。如果它是非空的,每個(gè)已取消的鍵的集合中的鍵將從另外兩
個(gè)集合中移除,并且相關(guān)的通道將被注銷。這個(gè)步驟結(jié)束后,已取消的鍵的集合將是空的。
已注冊(cè)的鍵的集合中的鍵的 interest 集合將被檢查。在這個(gè)步驟中的檢查執(zhí)行過(guò)后,對(duì)
interest 集合的改動(dòng)不會(huì)影響剩余的檢查過(guò)程。
a.如果通道的鍵還沒(méi)有處于已選擇的鍵的集合中,那么鍵的 ready 集合將被清空,然后表示操
作系統(tǒng)發(fā)現(xiàn)的當(dāng)前通道已經(jīng)準(zhǔn)備好的操作的比特掩碼將被設(shè)置。b.否則,也就是鍵在已選擇的鍵的集合中。鍵的 ready 集合將被表示操作系統(tǒng)發(fā)現(xiàn)的當(dāng)前已經(jīng)
準(zhǔn)備好的操作的比特掩碼更新。所有之前的已經(jīng)不再是就緒狀態(tài)的操作不會(huì)被清除。事實(shí)上,所有的比特位都不會(huì)被清理。由操作系統(tǒng)決定的 ready 集合是與之前的 ready 集合按位分離的,一旦鍵被放置于選擇器的已選擇的鍵的集合中,它的 ready 集合將是累積的。比特位只會(huì)被設(shè)置,不會(huì)被清理。
步驟 2 可能會(huì)花費(fèi)很長(zhǎng)時(shí)間,特別是所激發(fā)的線程處于休眠狀態(tài)時(shí)。與該選擇器相關(guān)的鍵可
能會(huì)同時(shí)被取消。當(dāng)步驟 2 結(jié)束時(shí),步驟 1 將重新執(zhí)行,以完成任意一個(gè)在選擇進(jìn)行的過(guò)程中,鍵
已經(jīng)被取消的通道的注銷。
select 操作返回的值是 ready 集合在步驟 2 中被修改的鍵的數(shù)量,而不是已選擇的鍵的集合中
的通道的總數(shù)。返回值不是已準(zhǔn)備好的通道的總數(shù),而是從上一個(gè) select( )調(diào)用之后進(jìn)入就緒狀態(tài)
的通道的數(shù)量。之前的調(diào)用中就緒的,并且在本次調(diào)用中仍然就緒的通道不會(huì)被計(jì)入,而那些在前
一次調(diào)用中已經(jīng)就緒但已經(jīng)不再處于就緒狀態(tài)的通道也不會(huì)被計(jì)入。這些通道可能仍然在已選擇的
鍵的集合中,但不會(huì)被計(jì)入返回值中。返回值可能是 0。
為什么延遲注銷
使用內(nèi)部的已取消的鍵的集合來(lái)延遲注銷,是一種防止線程在取消鍵時(shí)阻塞,并防止與正在進(jìn)
行的選擇操作沖突的優(yōu)化。注銷通道是一個(gè)潛在的代價(jià)很高的操作,這可能需要重新分配資源(請(qǐng)
記住,鍵是與通道相關(guān)的,并且可能與它們相關(guān)的通道對(duì)象之間有復(fù)雜的交互)。
僅僅在它們?cè)谒?cè)的通道當(dāng)前都沒(méi)有就緒時(shí),是否阻塞的方面有所不同。
select():在沒(méi)有通道就緒時(shí)將無(wú)限阻塞。一旦至少有一個(gè)已注冊(cè)的通道就緒,選擇器的選擇鍵
就會(huì)被更新,并且每個(gè)就緒的通道的 ready 集合也將被更新。返回值將會(huì)是已經(jīng)確定就緒的通道的
數(shù)目。正常情況下, 這些方法將返回一個(gè)零的值,因?yàn)橹钡揭粋€(gè)通道就緒前它都會(huì)阻塞。
select(long timeout):如果在您提供的超時(shí)時(shí)間(以毫秒計(jì)算)內(nèi)沒(méi)有通道就緒時(shí),它將返回 0。如果一個(gè)或者多個(gè)通道在時(shí)間限制終止前就緒,鍵的狀態(tài)將會(huì)被更新,并且方法會(huì)在那時(shí)立即返回。將超時(shí)參數(shù)指定為 0 表示將無(wú)限期等待,那么它就在各個(gè)方面都等同于使用select()
selectNow():執(zhí)行就緒檢查過(guò)程,但不阻塞。如果當(dāng)前沒(méi)有通道就緒,它將立即返回 0
停止選擇過(guò)程wakeUp()
某個(gè)線程調(diào)用select()方法后阻塞了,即使沒(méi)有通道已經(jīng)就緒,也有辦法讓其從select()方法返回。只要讓其它線程在第一個(gè)線程調(diào)用select()方法的那個(gè)對(duì)象上調(diào)用Selector.wakeup()方法即可。阻塞在select()方法上的線程會(huì)立馬返回。
如果有其它線程調(diào)用了wakeup()方法,但當(dāng)前沒(méi)有線程阻塞在select()方法上,下個(gè)調(diào)用select()方法的線程會(huì)立即“醒來(lái)(wake up)”。
close()
用完Selector后調(diào)用其close()方法會(huì)關(guān)閉該Selector,且使注冊(cè)到該Selector上的所有SelectionKey實(shí)例無(wú)效。通道本身并不會(huì)關(guān)閉。
interrupt()
如果睡眠中的線程的 interrupt( )方法被調(diào)用,它的返回狀態(tài)將被設(shè)置。如果被喚醒的線程之后
將試圖在通道上執(zhí)行 I/O 操作,通道將立即關(guān)閉,然后線程將捕捉到一個(gè)異常。
服務(wù)端
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOServer { // 通道管理器 private Selector selector; public void initServer(int port) throws Exception { // 獲得一個(gè)ServerSocket通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 設(shè)置通道為 非阻塞 serverChannel.configureBlocking(false); // 將該通道對(duì)于的serverSocket綁定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 獲得一耳光通道管理器 this.selector = Selector.open(); // 將通道管理器和該通道綁定,并為該通道注冊(cè)selectionKey.OP_ACCEPT事件 // 注冊(cè)該事件后,當(dāng)事件到達(dá)的時(shí)候,selector.select()會(huì)返回, // 如果事件沒(méi)有到達(dá)selector.select()會(huì)一直阻塞 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } // 采用輪訓(xùn)的方式監(jiān)聽(tīng)selector上是否有需要處理的事件,如果有,進(jìn)行處理 public void listen() throws Exception { System.out.println("start server"); // 輪詢?cè)L問(wèn)selector while (true) { // 當(dāng)注冊(cè)事件到達(dá)時(shí),方法返回,否則該方法會(huì)一直阻塞 selector.select(); // 獲得selector中選中的相的迭代器,選中的相為注冊(cè)的事件 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key 以防重負(fù)處理 ite.remove(); // 客戶端請(qǐng)求連接事件 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); // 獲得和客戶端連接的通道 SocketChannel channel = server.accept(); // 設(shè)置成非阻塞 channel.configureBlocking(false); // 在這里可以發(fā)送消息給客戶端 channel.write(ByteBuffer.wrap(new String("hello client").getBytes())); // 在客戶端 連接成功之后,為了可以接收到客戶端的信息,需要給通道設(shè)置讀的權(quán)限 channel.register(this.selector, SelectionKey.OP_READ); // 獲得了可讀的事件 } else if (key.isReadable()) { read(key); } } } } // 處理 讀取客戶端發(fā)來(lái)的信息事件 private void read(SelectionKey key) throws Exception { // 服務(wù)器可讀消息,得到事件發(fā)生的socket通道 SocketChannel channel = (SocketChannel) key.channel(); // 穿件讀取的緩沖區(qū) ByteBuffer buffer = ByteBuffer.allocate(10); channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("server receive from client: " + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer); } public static void main(String[] args) throws Throwable { NIOServer server = new NIOServer(); server.initServer(8989); server.listen(); } }
客戶端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOClient { // 通道管理器 private Selector selector; /** * * // 獲得一個(gè)Socket通道,并對(duì)該通道做一些初始化的工作 * @param ip 連接的服務(wù)器的ip // * @param port * 連接的服務(wù)器的端口號(hào) * @throws IOException */ public void initClient(String ip, int port) throws IOException { // 獲得一個(gè)Socket通道 SocketChannel channel = SocketChannel.open(); // 設(shè)置通道為非阻塞 channel.configureBlocking(false); // 獲得一個(gè)通道管理器 this.selector = Selector.open(); // 客戶端連接服務(wù)器,其實(shí)方法執(zhí)行并沒(méi)有實(shí)現(xiàn)連接,需要在listen()方法中調(diào) // 用channel.finishConnect();才能完成連接 channel.connect(new InetSocketAddress(ip, port)); // 將通道管理器和該通道綁定,并為該通道注冊(cè)SelectionKey.OP_CONNECT事件。 channel.register(selector, SelectionKey.OP_CONNECT); } /** * * // 采用輪詢的方式監(jiān)聽(tīng)selector上是否有需要處理的事件,如果有,則進(jìn)行處理 * @throws // IOException * @throws Exception */ @SuppressWarnings("unchecked") public void listen() throws Exception { // 輪詢?cè)L問(wèn)selector while (true) { // 選擇一組可以進(jìn)行I/O操作的事件,放在selector中,客戶端的該方法不會(huì)阻塞, // 這里和服務(wù)端的方法不一樣,查看api注釋可以知道,當(dāng)至少一個(gè)通道被選中時(shí), // selector的wakeup方法被調(diào)用,方法返回,而對(duì)于客戶端來(lái)說(shuō),通道一直是被選中的 selector.select(); // 獲得selector中選中的項(xiàng)的迭代器 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 刪除已選的key,以防重復(fù)處理 ite.remove(); // 連接事件發(fā)生 if (key.isConnectable()) { SocketChannel channel = (SocketChannel) key.channel(); // 如果正在連接,則完成連接 if (channel.isConnectionPending()) { channel.finishConnect(); } // 設(shè)置成非阻塞 channel.configureBlocking(false); // 在這里可以給服務(wù)端發(fā)送信息哦 channel.write(ByteBuffer.wrap(new String("hello server!").getBytes())); // 在和服務(wù)端連接成功之后,為了可以接收到服務(wù)端的信息,需要給通道設(shè)置讀的權(quán)限。 channel.register(this.selector, SelectionKey.OP_READ); // 獲得了可讀的事件 } else if (key.isReadable()) { read(key); } } } } private void read(SelectionKey key) throws Exception { SocketChannel channel = (SocketChannel) key.channel(); // 穿件讀取的緩沖區(qū) ByteBuffer buffer = ByteBuffer.allocate(10); channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("client receive msg from server:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer); } /** * * // 啟動(dòng)客戶端測(cè)試 * @throws IOException * @throws Exception */ public static void main(String[] args) throws Exception { NIOClient client = new NIOClient(); client.initClient("localhost", 8989); client.listen(); } }參考資料
Java NIO系列教程
Java NIO學(xué)習(xí)8(Selector)
更多內(nèi)容可以關(guān)注微信公眾號(hào),或者訪問(wèn)AppZone網(wǎng)站
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/64819.html
摘要:是一個(gè)用來(lái)替代標(biāo)準(zhǔn)的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會(huì)經(jīng)常存在他的身影。這個(gè)方法會(huì)一直阻塞到某個(gè)注冊(cè)的通道有事件就緒。保持不變,仍然表示能從中讀取多少個(gè)元素等與通過(guò)調(diào)用方法,可以標(biāo)記中的一個(gè)特定。 Java NIO是一個(gè)用來(lái)替代標(biāo)準(zhǔn)Java IO API的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會(huì)經(jīng)常存在他的身影。其比傳統(tǒng)的IO更加高效,非阻塞,異步,雙向 NIO主體結(jié)構(gòu) showIm...
摘要:而源碼解析系列文章則是會(huì)從源碼層面給大家抽絲剝繭,讓大家知道我們內(nèi)部到底是如何實(shí)現(xiàn)的。我們希望通過(guò)該源碼解析系列,能讓大家對(duì)有一個(gè)更深刻的理解。 作者:唐劉 TiKV 是一個(gè)支持事務(wù)的分布式 Key-Value 數(shù)據(jù)庫(kù),有很多社區(qū)開(kāi)發(fā)者基于 TiKV 來(lái)開(kāi)發(fā)自己的應(yīng)用,譬如 titan、tidis。尤其是在 TiKV 成為 CNCF 的 Sandbox 項(xiàng)目之后,吸引了越來(lái)越多開(kāi)發(fā)者的...
摘要:添加記錄操作步驟進(jìn)入域名解析頁(yè)面。,點(diǎn)擊添加記錄。,填寫詳細(xì)的記錄信息。配置說(shuō)明配置說(shuō)明主機(jī)記錄設(shè)置解析記錄的主機(jī)記錄名稱。標(biāo)準(zhǔn)應(yīng)答為返回設(shè)置的全部記錄值,支持全部記錄類型隨機(jī)應(yīng)答為根據(jù)權(quán)重隨機(jī)返回記錄值,僅支持記錄類型。添加記錄操作步驟1、進(jìn)入域名解析 UDNS頁(yè)面。2,點(diǎn)擊添加記錄。3,填寫詳細(xì)的記錄信息。詳細(xì)配置說(shuō)明見(jiàn)下方。4,點(diǎn)擊確定即可添加成功。配置說(shuō)明配置說(shuō)明主機(jī)記錄設(shè)置解析記錄...
摘要:安裝完成后登陸,注意,如果裝的是,協(xié)議改為。舉例我通過(guò)阿里云注冊(cè)的域名是那么這里我可以輸入,或者其他任何指定特定網(wǎng)站,一臺(tái)主機(jī)可以部署多個(gè)網(wǎng)站。 推薦安裝Xftp,是一個(gè)可視化管理云主機(jī)上文件的軟件,方便初學(xué)者學(xué)習(xí)。 安裝完成后登陸,showImg(https://segmentfault.com/img/bVZEAI?w=496&h=702); 注意,如果裝的是xftp 5,協(xié)議改為...
閱讀 1938·2019-08-29 16:44
閱讀 2231·2019-08-29 16:30
閱讀 864·2019-08-29 15:12
閱讀 3596·2019-08-26 10:48
閱讀 2715·2019-08-23 18:33
閱讀 3852·2019-08-23 17:01
閱讀 2006·2019-08-23 15:54
閱讀 1355·2019-08-23 15:05