1. 程式人生 > >【JAVA】NIO總結

【JAVA】NIO總結

Socket/ServerSocket

  • 一次性連線
    • socket 用完在兩端close 即可
  • 長連線
    • socket端
      • 傳送:在每次write到OutputStream時,使用flush();
      • 接收:用一個執行緒專門監聽socket物件的inputStream,如果available>0,則讀取裡面的資料
    • ServerSocket端
      • 接收: 用一個(或多個)執行緒監聽已連線的socket,如果inputStrea的available>0,說明有資料
      • 傳送:write完flush

SocketChannel/ServerSocketChannel

  • 阻塞模式 (blocking)
    • 一次性連線
      • socketChannel 用完在兩端close() 即可
    • 長連線
      • socketChannel端
        • 傳送:write即可;
        • 接收:用一個執行緒專門監聽socketChannel.read()>0,則讀取裡面的資料
      • ServerSocketChannel 端
        • 接收:用一個執行緒專門監聽socketChannel.read()>0,則讀取裡面的資料
        • 傳送:write即可;
  • 非阻塞模式 (non-blocking)
    • 見下文

  • 若是使用 阻塞模式的SocketChannel,與傳統Socket相比,兩者都是阻塞的;在使用長連線的方式時,Socket可以通過InputStream的avaliable()得到未讀位元組從而確定一次傳送是否結束, 而SocketChannel 由於無法獲得剩餘未讀位元組,不能判斷是都結束一次傳送。

    解決辦法

    • 傳送端write()時,在ByetBuffer 裡的約定位置寫入描述(可以是一個json的位元組陣列),儲存本次傳送byte的lenth、以及其他必要資訊。在接收端,按照約定先把描述json的位元組從ByteBuffer取出即可。
  • 如果通過Socket向ServerSocketChannel 傳送資料,也是按照SocketChannel一樣處理即可。

  • 網路Channel不支援直接緩衝區

DatagramChannel

~ ~
write\read 讀寫資料,必須在connected狀態下使用
send\receive 讀寫資料,可以在不connected狀態下使用,
但是這樣每次都會做安全檢查,
避免此項安全檢查開銷的方法是首先通過 connect 方法連線該套接字
  • 使用非阻塞模式的DatagramChannel時,需注意在註冊selector時,只有 OP_WRITE\OP_READ 是允許的
  • 呼叫connect方法,並不會真實的連線目標地址,但是DatagramChannel物件的isConnected會改變
  • read、write 只有在connected狀態下才可以使用,receive、send 不論是否connected狀態都可以使用,但是未連線狀態會多一步安全檢查。

Selector

selector 是實現非阻塞式Socket的核心。
~~ ~
open
register
select
wakeUp 如果有一個執行緒呼叫select()而阻塞,另一個執行緒呼叫該物件的wakeUp,
可使select()立即返回,即使沒有已就緒的selectedKey。
close 關閉selector,將會使註冊在該物件上的selectedKey失效,與之繫結的通道不會受影響

建立selector

Selector selector = Selector.open();

註冊channeld到selector

SelectionKey key = selector.register(channelObj,SelectionKey.OP_ACCEPT);
//register 返回監聽channelObj的SelectionKey物件
//SelectionKey物件將監聽channelObj的accept

獲取selector中已經就緒的SelectionKey數量

int num = selector.select();
/*
    //select方法返回已經就緒的channel數量
    int select()    阻塞到至少有一個已經就緒的channel
    int select(long timeout)    timeout表示阻塞時限
    int selectNow()     非阻塞,如果沒有就緒的cahnnel,返回0
*/

獲取selector中已經就緒的SelectionKey集合

Set set =selector.selectedKeys();

完整demo

//建立channel
ServerSocketChannel channelObj = ServerSocketChannel.open(7777);
//建立selector
Selector selector = Selector.open();
//註冊通道
SelectionKey key = selector.register(channelObj,SelectionKey.OP_ACCEPT);
while(selector.select()>0){
    Set set =selector.selectedKeys();
    //迭代已就緒的selectedKey
    Iterator<SelectionKey> iterator = set.iterator();
    while(iterator.hasNext()){
        SelectionKey key = set.next();
         //獲得與selectedKey繫結的通道
        Channel target = key.channel();
        //判斷就緒狀態
        if(key.isAcceptable()){

        }else if(key.isConnected()){

        }else if(key.isReadable()){

        }else if(key.isWritable()){

        }
        iterator.remove(); 
    }
     //已經處理過的就緒selectedKey需要移除,否則下次獲取selectedKeys集合時還會存在之前處理過的selectedKey
}

SelectionKey

靜態常量 ~
OP_CONNECT 連線就緒,channel成功連線
OP_ACCEPT 接收就緒,ServerSocketChannel 準備好接收
OP_READ 讀取就緒,通道有資料可讀
OP_WRITE 寫就緒
方法 ~
isAcceptable
isConnected
isReadable
isWritable
isVaild 返回 選擇鍵是否cancel或其選擇器是否close或註冊的通道是否關閉

cancel|請求取消此鍵的通道到其選擇器的註冊

  • SelectionKey用來與channel繫結,一個channel註冊到Slector物件中時,便會產生一個SelectionKey物件,通過它可以獲得channel處於何種就緒狀態

Pipe

Unix系統中,管道被用來連線一個程序的輸出和另一個程序的輸入。java中的Pipe是程序內(在Java虛擬機器程序內部)而非程序間使用的。

graph LR
A[A執行緒]-->|write|B[Pipe.Sink<br>Pipe.Source]
C[B執行緒]-->|read|B
    Pipe p = Pipe.open();
    //寫入
    p.sink().write(ByteBuffer.wrap("管道測試".getBytes()));
    //讀取
    ByteBuffer buff = ByteBuffer.allocate(1024);
    while(p.source().read(buff)>0) {
        System.out.println(new String(buff.array(),0,buff.limit()));
        buff.clear();
    }