小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

nio通道(4)--SocketChannel

 碧海山城 2012-09-02

從之前的通道(1)--基礎接口 大概知道了通道的基本特點,open/close/selectable/流式通道可以通過工廠方法open創(chuàng)建,文件通道比如在一個文件對象上調(diào)用getChannel來獲取,下面來深入看下SocketChannel

 

這一篇比較偏向理論

如果想知道大概的代碼怎么寫,可以參考 SocketChannel續(xù)1---基本操作API 

如果想知道更多的坑,可以參考 SocketChannel續(xù)2---很多注意點

如果想知道一個nio框架的演變,可以參考SocketChannel續(xù)3--io框架模型演化

1      SocketIO

1.1      阻塞式的

之前的Socket/ServerSocket/DatagramSocket他們是阻塞式的,java IO的性能瓶頸所在

1.2      不便的讀寫操作

另外讀寫也相對不便,分別是getInputStream() getOutputStream() 然后使用他們的readwrite方法,或者用XXReader稍微包裝一下,比如下面的

BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));

PrintWriter out = new PrintWriter(client.getOutputStream());

while (true) {

        String str = in.readLine();//socket讀入數(shù)據(jù)

        out.println("has receive...."+str);

        out.flush();

        if (str.equals("end"))

            break;

}

2           SocketChannel

通道是新增的IO服務管道,并且提供交互方法,但是socket已有的協(xié)議API并不會重新實現(xiàn),大多可以復用

 

全部Socket通道類(DatagramChannel/SocketChannel/ServerSocketChannel)在創(chuàng)建的時候都會創(chuàng)建一個對等的Socket對象,可以通過socket()方法獲取,這種方式獲取到的socket可以調(diào)用getChannel()獲取對應的channel

 

2.1      可選擇的通道

從最基礎的層面來看,選擇器提供了詢問通道是否準備好執(zhí)行每個I/O操作的能力,比如了解一個SocketChannel對象是否有更多的字節(jié)需要讀取,或者需要知道ServerSocketChannel是否有需要準備接受的連接。他們都實現(xiàn)了SelectableChannel接口,因此可以實現(xiàn)就緒選擇,這種方式的價值在于潛在的大量的通道同時進行就緒狀態(tài)的檢查。

 

2.2      自己實現(xiàn)選擇通道?

假設自己實現(xiàn)對是否就緒通道的輪訓,那么從效率上來說會有幾個問題1.檢查每個通道都需要一次系統(tǒng)調(diào)用,代價昂貴;2.檢查不是原子性的,列表中的每一個通道都有可能在它被檢查之后就緒,直到下一次輪訓為止;3.不斷地遍歷,無法在某個感興趣的通道就緒時得到通知;

傳統(tǒng)的監(jiān)控多個socket的方案是為每一個socket創(chuàng)建一個線程并使得線程在read中阻塞,直到數(shù)據(jù)可用。這里的被阻塞線程被當做了socket監(jiān)控器,java虛擬機的線程調(diào)度當做了通知機制,這種方式在線程數(shù)量的增長失控的時候會造成巨大的壓力。

因此真正的就緒選擇操作必須由操作系統(tǒng)來做,它會處理I/O請求并通知各個線程他們的數(shù)據(jù)已經(jīng)準備好了。

 

2.3      SocketChananel建立

SocketSocketChannel類封裝點對點、有序的網(wǎng)絡連接;每個SocketChannel對象創(chuàng)建時都是和一個對等的Socket對象關聯(lián)的,靜態(tài)的open方法可以創(chuàng)建一個新的SocketChannel(注意Socket的通道都是通過工廠方法open創(chuàng)建的),在Channel上調(diào)用socket方法能返回對等的Socket對象

新創(chuàng)建的Channel都是未連接的,可以調(diào)用connect方法去連接,連接之前嘗試IO操作會導致NotYetConnectedException異常。如果說阻塞模式下,線程在連接建立好或超時之前會保持阻塞;在非阻塞模式下(沒有超時的參數(shù)),他會發(fā)起連接請求,并且立即返回,如果是true,則說明連接已經(jīng)建立(本地環(huán)回連接);如果不能連接,立即返回false,并且異步的繼續(xù)嘗試連接,這時候isConnectPending()會返回true,這時候可以調(diào)用finishConnect()來安全的完成連接過程

1.connect還未被調(diào)用,拋出NoConnectionPendingException

2.正在進行連接,未完成,那么finishConnect會立即返回false

3.非阻塞模式下,調(diào)用connect之后,SocketChannel可以調(diào)用configureBlocking()切換回阻塞模式,這時候調(diào)用finishConnect方法會阻塞直到連接建立完成

4.如果連接已經(jīng)建立,那么調(diào)用finishConnect()方法會返回true,什么也不發(fā)生

 

當通道處于中間的連接等待(connectio n-pending)狀態(tài)時,只可以調(diào)用 finishConnect( ) 、isConnectPending( )isConnected( ) 方法。一旦連接建立過程成功完成,isConnected( ) 將返回 true值。

 

InetSocketAddress addr = new InetSocketAddress (host, port);
SocketChannel sc = SocketChannel.open( );
sc.configureBlocking (false);
sc.connect (addr);
while ( ! sc.finishConnect( )) {
     doSomethingElse( );
}
doSomethingWithChannel (sc);
sc.close( );

 

3           三個基本元素

 
(Selector、SelectionKey、SocketChannel之間的關系)
 
(在使用nio的情況下,常會接觸的API)
 

3.1      Selector

它管理著一組被注冊的通道集合的信息和他們的就緒狀態(tài)

 
3.2     
SelectableChannel

這個抽象類提供了實現(xiàn)通道的可選擇性所需要的公共方法。它是所有支持就緒檢查的通道類的父類;一個通道可以被注冊到多個選擇器上,可以通過isRegistered來檢查一個通道是否被注冊到任何一個選擇器上,但對每個選擇器而言只能被注冊一次。

(注冊之前要確保通道是非阻塞的,否則拋出異常)

SelectionKey wKey = channel.register(selector, SelectionKey.OP_WRITE);

 

3.3      SelectorKey

選擇鍵封裝了特定的通道與特定的選擇器的注冊關系,可以調(diào)用cancel方法終結這種關系。在通道的特定事件注冊到選擇器上之后,該選擇鍵對象被返回并提供一個表示這種注冊關系的標記,通過它可以得到channel,包括一些狀態(tài)等。

 

選擇鍵是基于位的操作,比如判斷是否有寫事件,如下代碼

public final boolean isWritable() {

        return (readyOps() & OP_WRITE) != 0;

}

4           深入

4.1      selector創(chuàng)建和注冊

ServerSocketChannel server = ServerSocketChannel.open();

Selector sel = Selector.open();// 創(chuàng)建,使用完畢之后調(diào)用close關閉

 

server.socket().bind(new InetSocketAddress(port));

server.configureBlocking(false); // 設置成非阻塞

 

// 注冊感興趣的事件,可以關聯(lián)一個對象,下次通過SelectorKey獲?。ńoServer注冊read事件其實是無效的)

SelectionKey sk=server.register(sel, SelectionKey.OP_ACCEPT);

SelectionKey sk1=server.register(sel, SelectionKey.OP_READ&SelectionKey.OP_ACCEPT);

       

//任意時刻只有一種注冊關系是有效的,實際上只是更新selectionkey的感興趣集合,并不是新創(chuàng)建

//keyFor返回channel和該Selector的選擇鍵

Assert.assertTrue(server.keyFor(sel)==sk);

Assert.assertTrue(server.keyFor(sel)==sk1);

 

int count=selector.select(100);

 

Select是一個阻塞操作,他會等待直到有感興趣的事件,或者超過100m

 

注意到Selector是通過open這個工廠方法創(chuàng)建的,他會通過SelectorProvider來獲取一個新的實例,關于SPI機制,請參考:Java SPI機制簡介

 

4.2   selector的選擇鍵集合以及選擇過程

前面我們知道了,選擇鍵(SelectionKey)代表了channelSelector的注冊關系,可以調(diào)用cancel()取消:

//取消channelSelector的注冊關系

sk1.cancel();

不過這種注銷關系并不是立即生效的,實際上,選擇器(Selector)會維護三種選擇鍵(SelectionKey)的集合

 

1.已注冊的鍵的集合(Registered key set,可能包括已經(jīng)取消的鍵,通過keys()方法返回,無法修改

2.已準備好的選擇鍵集合(Selected key set,1的子集,每個成員都是選擇器判斷相關的通道已經(jīng)準備好,并且包含于鍵的interest集合中的某種操作(比如read),通過selectedKeys方法返回。如果要確定是具體某種操作,請使用readyOps確定。

3.已取消的鍵集合,但是還沒被注銷

 

當一個selector操作被調(diào)用的時候,會發(fā)生下面的事情:

1.檢查已取消的鍵集合,如果非空,則將這鍵從另外兩個集合中移出

 

2.檢查已注冊的鍵集合,每個鍵的interest事件集合會被檢查,之后修改interest也不會影響后面的過程,然后會執(zhí)行底層的查詢,直到有感興趣的事件或者超時

a)   在操作系統(tǒng)確定一個鍵的某個interest事件發(fā)生的時候,會確定這個鍵有沒有在已選擇的鍵集合中,如果沒,則鍵加入到選擇的集合,清空ready集合,并且設置該感興趣的事件到ready

b)   如果已經(jīng)在已選擇的鍵中,那么更新ready(位操作)

c)   之后會重新執(zhí)行1,這樣可以取消那些在選擇過程中有變更的通道

 

3.返回的是從上一個select之后,處于就緒狀態(tài)的通道數(shù)量,如果已經(jīng)在就緒集合中,不會累計;比如我們注冊read,并且在使用之后SelectionKey不移出,那么下次再有read事件過來,select方法可能會返回0就不會被記錄,但是不代表沒有感興趣的事件

 

延遲注銷鍵的操作,是為了在選擇的過程中減少不必要的同步,不然注銷和選擇就要形成一定的互斥,因為注銷潛在的代價比較高,可能需要釋放各種資源。關于SelectionKeyreadinterest集合可以參考4.4

4.3   喚醒正在阻塞的selector操作

調(diào)用wakeup可以使阻塞在select方法的線程返回,當然如果當前沒有阻塞的select方法,那么會讓下一次select直接返回,他調(diào)用多次和一次的效果是一樣的

 

不過很多時候,我們并不能確定是否有線程阻塞在select方法,也不想影響到下一次select(只是因為某些事件,臨時喚醒一下),那可以在wakeup之后調(diào)用selectNow,他會立即返回,也抵消了wakeup的影響

 

4.4   支持的事件

在使用Selector的時候,需要將一個通道注冊到上面,即:(通道在注冊之前需要設置為非阻塞)

Selector selector = Selector.open( );

server.register(channel, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

2個參數(shù)表示關心的通道操作,有4種:讀(read)、寫(write)、連接(connect)、接受(accept;

 

不是所有的Channel都支持這些事件,比如SocketChannel不支持accept,可以通過validOps()來驗證特定的通道所支持的操作集合。

Socket channels support connecting, reading, and writing, so this method returns (SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE).

Server-socket channels only support the accepting of new connections, so this method returns SelectionKey.OP_ACCEPT.

 

4.5   SelectionKey使用

 

4.5.1          interestready集合

一個Key包含兩個以整數(shù)形式進行編碼的比特掩碼,

interest(集合):指示哪些通道/選擇器組合所關心的操作

ready(集合):表示通道準備好要執(zhí)行的操作

 

當前的interest集合可以通過調(diào)用鍵對象的interestOps()方法來獲取,這個值不會被選擇器改變,但是可以調(diào)用帶參數(shù)的interestOps()方法改變,和上面一樣,他會在下一次selector的時候生效;

 

key.interestOps()返回的是注冊到該channel上的感興趣的動作,key.readyOps()是該channel已經(jīng)就緒的操作,ready集合是interest集合的子集,并且表示了interest集合中從上次調(diào)用select()以來已經(jīng)就緒的那些操作,例如:注冊channel感興趣的動作是OP_READ,OP_WRITE,

sc.register(sel, SelectionKey.OP_WRITE|SelectionKey.OP_READ);

調(diào)用interestOps()可以得到他對readwrite感興趣,但是如果該channel中沒有數(shù)據(jù),則只能是key.readyOps()==SelectionKey.OP_WRITE。



比如下面的方法檢查通道是否已經(jīng)可讀,并且讀取

if ((key.readyOps( ) & SelectionKey.OP_READ) != 0){    

            myBuffer.clear( );    

            key.channel( ).read (myBuffer);    

            doSomethingWithBuffer (myBuffer.flip( ));

 

 

另外key也有便捷的方式可以檢查:

if (key.isWritable( ))
等價于:
if ((key.readyOps( ) & SelectionKey.OP_WRITE) != 0)

 

另外,SelectionKey中還有一個attach方法啊,可以獲得鍵對象中保存所提供的對象的引用

 

4.5.2  移除SelectionKey


為了表示已經(jīng)處理了ready,只能將該SelectionKey從已選擇的鍵集合中移出。

Iterator<SelectionKey> iter = selector.selectedKeys().iterator();

while (iter.hasNext()) {

  /**

SelectionKey中的ready集合表示了他感興趣的事件,他只能在選擇的時候由底層修改,其他時候無  

 法修改,他表示的是合法的就緒信息(而不是可以任意設置的),

 所以為了表示已經(jīng)處理了ready,只能將該SelectionKey從已選擇的鍵集合中移出


另外,如果通道關閉,因為SelectionKey表示通道和selector的關系,

所以永遠都會發(fā)生“關閉”事件,除非通道從slector移除(key.cancel())

*/

    SelectionKey key = iter.next();

    iter.remove();

    handleKey(key);

}


// 處理事件,Key可以同時表示多個事件到達

protected void handleKey(SelectionKey key) throws IOException {

    if (key.isAcceptable()) { 

        // 允許網(wǎng)絡連接事件

        ServerSocketChannel server = (ServerSocketChannel) key.channel();

        SocketChannel channel = server.accept();

        channel.configureBlocking(false);

        // 網(wǎng)絡管道準備處理讀事件

        channel.register(selector, SelectionKey.OP_READ);

    else if (key.isReadable()) { 

    else if (key.isWritable()) {

        SocketChannel channel = (SocketChannel) key.channel();

    }

}


 

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多