成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JAVA_NIO詳細(xì)解析說明

SillyMonkey / 1104人閱讀

摘要:通道是和選擇器一起被注冊的,并且使用選擇器來更新通道的就緒狀態(tài)。注冊不會立即被取消,但鍵會立即失效。這個集合的每個成員都是相關(guān)的通道被選擇器在前一個選擇操作中判斷為已經(jīng)準(zhǔn)備好的,并且包

Java NIO是一個用來替代標(biāo)準(zhǔn)Java IO API的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會經(jīng)常存在他的身影。其比傳統(tǒng)的IO更加高效,非阻塞,異步,雙向

NIO主體結(jié)構(gòu)

Java NIO的主要構(gòu)成核心就是Buffer、Channel和Selector這三個

對于Channel我想要提醒的是,Channel中的數(shù)據(jù)總是要先讀到一個Buffer,或者總是要從一個Buffer中寫入

使用Selector,得向Selector注冊Channel,然后調(diào)用它的select()方法。這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件

Channel

所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點(diǎn)象流

Channel的實(shí)現(xiàn)

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ù)器那樣。對每一個新進(jìn)來的連接都會創(chuàng)建一個SocketChannel

Scatter/Gather

分散(scatter)從Channel中讀取是指在讀操作時將讀取的數(shù)據(jù)寫入多個buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個Buffer中

聚集(gather)寫入Channel是指在寫操作時將多個buffer的數(shù)據(jù)寫入同一個Channel,因此,Channel 將多個Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel

通過這樣的方式可以方便數(shù)據(jù)的讀取,當(dāng)你想要獲取整個數(shù)據(jù)的一部分的時候,通過這種方式可以很快的獲取數(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)一個buffer被寫滿后,channel緊接著向另一個buffer中寫

transferFrom、transferTo

實(shí)現(xiàn)兩個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處開始向目標(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作用是一樣的,只是一個是tochannal調(diào)用,一個是fromchannnal調(diào)用

在實(shí)際的運(yùn)用中可能存在源通道的剩余空間小于 count 個字節(jié),則所傳輸?shù)淖止?jié)數(shù)要小于請求的字節(jié)數(shù)

在SoketChannel的實(shí)現(xiàn)中,SocketChannel只會傳輸此刻準(zhǔn)備好的數(shù)據(jù)(可能不足count字節(jié))。因此,SocketChannel可能不會將請求的所有數(shù)據(jù)(count個字節(jié))全部傳輸?shù)紽ileChannel中

看官一定要仔細(xì)看我栗子中的注釋

Buffer

Buffer是一個緩存區(qū),其會將Channel中的數(shù)據(jù)存儲起來

Buffer的實(shí)現(xiàn)

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

MappedByteBuffer

capacity,position,limit

在講解該主題之前,首先要明白讀模式和寫模式,無論是Channel還是Buffer都存在這兩種模式,要理解這兩種模式,第一步要明確主題是哪一個,是Channel還是Buffer。舉個栗子,主角是Channel,讀模式的含義就是從Buffer中獲取數(shù)據(jù),寫模式就是將數(shù)據(jù)寫入Buffer,對于Buffer則是相反。搞清楚這一點(diǎn),理解下面的就要相對清楚一點(diǎn)

capacity:作為一個內(nèi)存塊,其就代表了當(dāng)前Buffer能最多暫存多少數(shù)據(jù)量,存儲的數(shù)據(jù)類型則是根據(jù)上面的Buffer對象類型,一旦Buffer滿了,需要將其清空(通過讀數(shù)據(jù)或者清除數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)往里寫數(shù)據(jù)

position:代表當(dāng)前數(shù)據(jù)讀或?qū)懱幱谀莻€位置。讀模式:被重置從0開始,最大值可能為capacity-1或者limit-1,寫模式:被重置從0開始,最大值為limit-1

limit:最多能往Buffer里寫多少數(shù)據(jù),limit大小跟數(shù)據(jù)量大小和capacity有關(guān),讀模式:數(shù)據(jù)量>capacity時,limit=capacity,數(shù)據(jù)量=capacity時,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每次可以存儲數(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():清空整個緩沖區(qū)

compact():只會清除已經(jīng)讀過的數(shù)據(jù)。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面

allocate(1024):初始化Buffer,設(shè)定的值就決定capacity值的大小

rewind():將position設(shè)回0,所以你可以重讀Buffer中的所有數(shù)據(jù)。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)

mark()與reset():通過調(diào)用Buffer.mark()方法,可以標(biāo)記Buffer中的一個特定position。之后可以通過調(diào)用Buffer.reset()方法恢復(fù)到這個position

equals():當(dāng)滿足下面三個條件時,兩個Buffer才是相等

有相同的類型(byte、char、int等)

Buffer中剩余的byte、char等的個數(shù)相等

Buffer中所有剩余的byte、char等都相同

只比較的是剩余的數(shù)據(jù)

compareTo():滿足下列條件,則認(rèn)為一個Buffer“小于”另一個Buffer

第一個不相等的元素小于另一個Buffer中對應(yīng)的元素

所有元素都相等,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數(shù)比另一個少)

Selector

Selector允許單線程處理多個 Channel。如果你的應(yīng)用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便

大致流程

當(dāng)您調(diào)用一個選擇器對象的 select( )方法時,相關(guān)的鍵會被更新,用來檢查所有被注冊到該選擇器的通道。您可以獲取一個鍵的集合,從而找到當(dāng)時已經(jīng)就緒的通道。通過遍歷這些鍵,您可以選擇出每個從上次您調(diào)用 select( )開始直到現(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è)置無效
public abstract SelectionProvider provider( );
}

選擇器類管理著一個被注冊的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊
的,并且使用選擇器來更新通道的就緒狀態(tài)。當(dāng)這么做的時候,可以選擇將被激發(fā)的線程掛起,直

到有就緒的的通道

不能注冊已經(jīng)關(guān)閉的selectableChannel

通過調(diào)用一個自定義的 SelectorProvider對象的 openSelector( )方法來創(chuàng)建一個 Selector 實(shí)例也是可行的。您可以通過調(diào)用 provider( )方法來決定由哪個 SelectorProvider 對象來創(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

一個channel可以注冊到多個selector中

一個selector中同一個channel只能有一個

通道被注冊前,要非阻塞模式

支持Connect、Accept、Read、Write四種可選擇操作事件,但并不是所有的SelectableChannel都存在以上四類,可以通過validOps()獲取可以使用的操作事件集合

如果你對不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來

任何一個通道和選擇器的注冊關(guān)系都被封裝在一個 SelectionKey 對象中。 keyFor( )方法將
返回與該通道和指定的選擇器相關(guān)的鍵。如果通道被注冊到指定的選擇器上,那么相關(guān)的鍵將被返

回。如果它們之間沒有注冊關(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( )
}

封裝了特定的通道與特定的選擇器的注冊關(guān)系

一個 SelectionKey 對象包含兩個以整數(shù)形式進(jìn)行編碼的byte掩碼:一個用于指示那些通道/
選擇器組合體所關(guān)心的操作(instrest 集合),另一個表示通道準(zhǔn)備好要執(zhí)行的操作( ready 集合)

當(dāng)終結(jié)注冊關(guān)系時

當(dāng)應(yīng)該終結(jié)這種關(guān)系的時候,可以調(diào)用 SelectionKey對象的 cancel( )方法??梢酝ㄟ^調(diào)用 isValid( )方法來檢查它是否仍然表示一種有效的關(guān)系。當(dāng)鍵被取消時,它將被放在相關(guān)的選擇器的已取消的鍵的集合里。注冊不會立即被取消,但鍵會立即失效。當(dāng)再次調(diào)用 select( )方法時(或者一個正在進(jìn)行的 select()調(diào)用結(jié)束時),已取消的鍵的集合中的被取消的鍵將被清理掉,并且相應(yīng)的注銷也將完成。通道會被注銷,而新的SelectionKey 將被返回

當(dāng)通道關(guān)閉時

當(dāng)通道關(guān)閉時,所有相關(guān)的鍵會自動取消(記住,一個通道可以被注冊到多個選擇器上)。當(dāng)
選擇器關(guān)閉時,所有被注冊到該選擇器的通道都將被注銷,并且相關(guān)的鍵將立即被無效化(取
消)。一旦鍵被無效化,調(diào)用它的與選擇相關(guān)的方法就將拋出 CancelledKeyException

interest 集合

當(dāng)前的 interest 集合可以通過調(diào)用鍵對象的 interestOps( )方法來獲取

最初,這應(yīng)該是通道被注冊時傳進(jìn)來的值。這個 interset 集合永遠(yuǎn)不會被選擇器改變,但您可以通過調(diào)用 interestOps( )方法并傳入一個新的byte掩碼參數(shù)來改變它。 interest 集合也可以通過將通道注冊到選擇器上來改變(實(shí)際上使用一種迂回的方式調(diào)用 interestOps( )),就像 4.1.2 小節(jié)中描的那樣。當(dāng)相關(guān)的 Selector 上的 select( )操作正在進(jìn)行時改變鍵的 interest 集合,不會影響那個正在進(jìn)行的選擇操作。所有更改將會在 select( )的下一個調(diào)用中體現(xiàn)出來

ready集合

可以通過調(diào)用鍵的 readyOps( )方法來獲取相關(guān)的通道的已經(jīng)就緒的操作。 ready 集合是 interest
集合的子集,并且表示了 interest 集合中從上次調(diào)用 select( )以來已經(jīng)就緒的那些操作

SelectionKey 類定義了四個便于使用的布爾方法來為您測試這些byte值: isReadable( ), isWritable( ), isConnectable( ), 和 isAcceptable( )

SelectionKey 對象包含的 ready 集合與最近一次選擇器對所注冊的通道所作的檢查相同。而每個多帶帶的通道的就緒狀態(tài)會同時改變

附加的對象

可以將一個對象或者更多信息附著到SelectionKey上,這樣就能方便的識別某個給定的通道。例如,可以附加 與通道一起使用的Buffer,或是包含聚集數(shù)據(jù)的某個對象。使用方法如下:

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

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

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

如果選擇鍵的存續(xù)時間很長,但您附加的對象不應(yīng)該存在那么長時間,請記得在完成后清理附件。否則,您附加的對象將不能被垃圾回收,您將會面臨內(nèi)存泄漏問題

總體上說, SelectionKey 對象是線程安全的,但知道修改 interest 集合的操作是通過 Selector 對象進(jìn)行同步的是很重要的。這可能會導(dǎo)致 interestOps( )方法的調(diào)用會阻塞不確定長的一段時間。選擇器所使用的鎖策略(例如是否在整個選擇過程中保持這些鎖)是依賴于具體實(shí)現(xiàn)的。幸好,這種多元處理能力被特別地設(shè)計(jì)為可以使用單線程來管理多個通道。被多個線程使用的選擇器也只會在系統(tǒng)特別復(fù)雜時產(chǎn)生問題。

選擇過程
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( );
}

已注冊的鍵的集合

與選擇器關(guān)聯(lián)的已經(jīng)注冊的鍵的集合。并不是所有注冊過的鍵都仍然有效。這個集合通過
keys( )方法返回,并且可能是空的。這個已注冊的鍵的集合不是可以直接修改的;試圖這么做的話
將引 java.lang.UnsupportedOperationException。

已選擇的鍵的集合

已注冊的鍵的集合的子集。這個集合的每個成員都是相關(guān)的通道被選擇器(在前一個選擇操作
中)判斷為已經(jīng)準(zhǔn)備好的,并且包含于鍵的 interest 集合中的操作。這個集合通過 selectedKeys( )方
法返回(并有可能是空的)

不要將已選擇的鍵的集合與 ready 集合弄混了。這是一個鍵的集合,每個鍵都關(guān)聯(lián)一個已經(jīng)準(zhǔn)
備好至少一種操作的通道。每個鍵都有一個內(nèi)嵌的 ready 集合,指示了所關(guān)聯(lián)的通道已經(jīng)準(zhǔn)備好的
操作

鍵可以直接從這個集合中移除,但不能添加

已取消的鍵的集合

已注冊的鍵的集合的子集,這個集合包含了 cancel( )方法被調(diào)用過的鍵(這個鍵已經(jīng)被無效
化),但它們還沒有被注銷。這個集合是選擇器對象的私有成員,因而無法直接訪問

在一個剛初始化的 Selector 對象中,這三個集合都是空的。

執(zhí)行步驟

已取消的鍵的集合將會被檢查。如果它是非空的,每個已取消的鍵的集合中的鍵將從另外兩
個集合中移除,并且相關(guān)的通道將被注銷。這個步驟結(jié)束后,已取消的鍵的集合將是空的。

已注冊的鍵的集合中的鍵的 interest 集合將被檢查。在這個步驟中的檢查執(zhí)行過后,對
interest 集合的改動不會影響剩余的檢查過程。

a.如果通道的鍵還沒有處于已選擇的鍵的集合中,那么鍵的 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)的操作不會被清除。事實(shí)上,所有的比特位都不會被清理。由操作系統(tǒng)決定的 ready 集合是與之前的 ready 集合按位分離的,一旦鍵被放置于選擇器的已選擇的鍵的集合中,它的 ready 集合將是累積的。比特位只會被設(shè)置,不會被清理。

步驟 2 可能會花費(fèi)很長時間,特別是所激發(fā)的線程處于休眠狀態(tài)時。與該選擇器相關(guān)的鍵可
能會同時被取消。當(dāng)步驟 2 結(jié)束時,步驟 1 將重新執(zhí)行,以完成任意一個在選擇進(jìn)行的過程中,鍵

已經(jīng)被取消的通道的注銷。

select 操作返回的值是 ready 集合在步驟 2 中被修改的鍵的數(shù)量,而不是已選擇的鍵的集合中
的通道的總數(shù)。返回值不是已準(zhǔn)備好的通道的總數(shù),而是從上一個 select( )調(diào)用之后進(jìn)入就緒狀態(tài)

的通道的數(shù)量。之前的調(diào)用中就緒的,并且在本次調(diào)用中仍然就緒的通道不會被計(jì)入,而那些在前
一次調(diào)用中已經(jīng)就緒但已經(jīng)不再處于就緒狀態(tài)的通道也不會被計(jì)入。這些通道可能仍然在已選擇的
鍵的集合中,但不會被計(jì)入返回值中。返回值可能是 0。

為什么延遲注銷

使用內(nèi)部的已取消的鍵的集合來延遲注銷,是一種防止線程在取消鍵時阻塞,并防止與正在進(jìn)
行的選擇操作沖突的優(yōu)化。注銷通道是一個潛在的代價很高的操作,這可能需要重新分配資源(請
記住,鍵是與通道相關(guān)的,并且可能與它們相關(guān)的通道對象之間有復(fù)雜的交互)。

三種select()方法

僅僅在它們在所注冊的通道當(dāng)前都沒有就緒時,是否阻塞的方面有所不同。

select():在沒有通道就緒時將無限阻塞。一旦至少有一個已注冊的通道就緒,選擇器的選擇鍵
就會被更新,并且每個就緒的通道的 ready 集合也將被更新。返回值將會是已經(jīng)確定就緒的通道的

數(shù)目。正常情況下, 這些方法將返回一個零的值,因?yàn)橹钡揭粋€通道就緒前它都會阻塞。

select(long timeout):如果在您提供的超時時間(以毫秒計(jì)算)內(nèi)沒有通道就緒時,它將返回 0。如果一個或者多個通道在時間限制終止前就緒,鍵的狀態(tài)將會被更新,并且方法會在那時立即返回。將超時參數(shù)指定為 0 表示將無限期等待,那么它就在各個方面都等同于使用select()

selectNow():執(zhí)行就緒檢查過程,但不阻塞。如果當(dāng)前沒有通道就緒,它將立即返回 0

停止選擇過程

wakeUp()

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

如果有其它線程調(diào)用了wakeup()方法,但當(dāng)前沒有線程阻塞在select()方法上,下個調(diào)用select()方法的線程會立即“醒來(wake up)”。

close()

用完Selector后調(diào)用其close()方法會關(guān)閉該Selector,且使注冊到該Selector上的所有SelectionKey實(shí)例無效。通道本身并不會關(guān)閉。

interrupt()

如果睡眠中的線程的 interrupt( )方法被調(diào)用,它的返回狀態(tài)將被設(shè)置。如果被喚醒的線程之后
將試圖在通道上執(zhí)行 I/O 操作,通道將立即關(guān)閉,然后線程將捕捉到一個異常。

例子

服務(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 {
        // 獲得一個ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 設(shè)置通道為 非阻塞
        serverChannel.configureBlocking(false);
        // 將該通道對于的serverSocket綁定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 獲得一耳光通道管理器
        this.selector = Selector.open();

        // 將通道管理器和該通道綁定,并為該通道注冊selectionKey.OP_ACCEPT事件
        // 注冊該事件后,當(dāng)事件到達(dá)的時候,selector.select()會返回,
        // 如果事件沒有到達(dá)selector.select()會一直阻塞

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    // 采用輪訓(xùn)的方式監(jiān)聽selector上是否有需要處理的事件,如果有,進(jìn)行處理
    public void listen() throws Exception {
        System.out.println("start server");
        // 輪詢訪問selector
        while (true) {
            // 當(dāng)注冊事件到達(dá)時,方法返回,否則該方法會一直阻塞
            selector.select();
            // 獲得selector中選中的相的迭代器,選中的相為注冊的事件
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key 以防重負(fù)處理
                ite.remove();
                // 客戶端請求連接事件
                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ā)來的信息事件
    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;

    /**
     * * // 獲得一個Socket通道,并對該通道做一些初始化的工作 * @param ip 連接的服務(wù)器的ip // * @param port
     * 連接的服務(wù)器的端口號 * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException { // 獲得一個Socket通道
        SocketChannel channel = SocketChannel.open(); // 設(shè)置通道為非阻塞
        channel.configureBlocking(false); // 獲得一個通道管理器
        this.selector = Selector.open(); // 客戶端連接服務(wù)器,其實(shí)方法執(zhí)行并沒有實(shí)現(xiàn)連接,需要在listen()方法中調(diào)
        // 用channel.finishConnect();才能完成連接
        channel.connect(new InetSocketAddress(ip, port));
        // 將通道管理器和該通道綁定,并為該通道注冊SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * * // 采用輪詢的方式監(jiān)聽selector上是否有需要處理的事件,如果有,則進(jìn)行處理 * @throws // IOException
     * @throws Exception 
     */
    @SuppressWarnings("unchecked")
    public void listen() throws Exception { // 輪詢訪問selector
        while (true) {
            // 選擇一組可以進(jìn)行I/O操作的事件,放在selector中,客戶端的該方法不會阻塞,
            // 這里和服務(wù)端的方法不一樣,查看api注釋可以知道,當(dāng)至少一個通道被選中時,
            // selector的wakeup方法被調(diào)用,方法返回,而對于客戶端來說,通道一直是被選中的
            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);

    }

    /**
     * * // 啟動客戶端測試 * @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)注微信公眾號,或者訪問AppZone網(wǎng)站

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/64819.html

相關(guān)文章

  • JAVA_NIO系列——Channel和Buffer詳解

    摘要:是一個用來替代標(biāo)準(zhǔn)的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會經(jīng)常存在他的身影。這個方法會一直阻塞到某個注冊的通道有事件就緒。保持不變,仍然表示能從中讀取多少個元素等與通過調(diào)用方法,可以標(biāo)記中的一個特定。 Java NIO是一個用來替代標(biāo)準(zhǔn)Java IO API的新型數(shù)據(jù)傳遞方式,像現(xiàn)在分布式架構(gòu)中會經(jīng)常存在他的身影。其比傳統(tǒng)的IO更加高效,非阻塞,異步,雙向 NIO主體結(jié)構(gòu) showIm...

    leon 評論0 收藏0
  • TiKV 源碼解析系列文章(一)序

    摘要:而源碼解析系列文章則是會從源碼層面給大家抽絲剝繭,讓大家知道我們內(nèi)部到底是如何實(shí)現(xiàn)的。我們希望通過該源碼解析系列,能讓大家對有一個更深刻的理解。 作者:唐劉 TiKV 是一個支持事務(wù)的分布式 Key-Value 數(shù)據(jù)庫,有很多社區(qū)開發(fā)者基于 TiKV 來開發(fā)自己的應(yīng)用,譬如 titan、tidis。尤其是在 TiKV 成為 CNCF 的 Sandbox 項(xiàng)目之后,吸引了越來越多開發(fā)者的...

    LeviDing 評論0 收藏0
  • 【云解析 UDNS】操作指南:添加記錄

    摘要:添加記錄操作步驟進(jìn)入域名解析頁面。,點(diǎn)擊添加記錄。,填寫詳細(xì)的記錄信息。配置說明配置說明主機(jī)記錄設(shè)置解析記錄的主機(jī)記錄名稱。標(biāo)準(zhǔn)應(yīng)答為返回設(shè)置的全部記錄值,支持全部記錄類型隨機(jī)應(yīng)答為根據(jù)權(quán)重隨機(jī)返回記錄值,僅支持記錄類型。添加記錄操作步驟1、進(jìn)入域名解析 UDNS頁面。2,點(diǎn)擊添加記錄。3,填寫詳細(xì)的記錄信息。詳細(xì)配置說明見下方。4,點(diǎn)擊確定即可添加成功。配置說明配置說明主機(jī)記錄設(shè)置解析記錄...

    Tecode 評論0 收藏0
  • 【項(xiàng)目上線】詳細(xì)步驟04:在一臺云主機(jī)上部署多個網(wǎng)站,通過自定義網(wǎng)站名訪問項(xiàng)目地址

    摘要:安裝完成后登陸,注意,如果裝的是,協(xié)議改為。舉例我通過阿里云注冊的域名是那么這里我可以輸入,或者其他任何指定特定網(wǎng)站,一臺主機(jī)可以部署多個網(wǎng)站。 推薦安裝Xftp,是一個可視化管理云主機(jī)上文件的軟件,方便初學(xué)者學(xué)習(xí)。 安裝完成后登陸,showImg(https://segmentfault.com/img/bVZEAI?w=496&h=702); 注意,如果裝的是xftp 5,協(xié)議改為...

    Jrain 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<