【JAVA】NIO總結
阿新 • • 發佈:2018-12-25
Socket/ServerSocket
- 一次性連線
- socket 用完在兩端close 即可
- 長連線
- socket端
- 傳送:在每次write到OutputStream時,使用flush();
- 接收:用一個執行緒專門監聽socket物件的inputStream,如果available>0,則讀取裡面的資料
- ServerSocket端
- 接收: 用一個(或多個)執行緒監聽已連線的socket,如果inputStrea的available>0,說明有資料
- 傳送:write完flush
- socket端
SocketChannel/ServerSocketChannel
- 阻塞模式 (blocking)
- 一次性連線
- socketChannel 用完在兩端close() 即可
- 長連線
- socketChannel端
- 傳送:write即可;
- 接收:用一個執行緒專門監聽socketChannel.read()>0,則讀取裡面的資料
- ServerSocketChannel 端
- 接收:用一個執行緒專門監聽socketChannel.read()>0,則讀取裡面的資料
- 傳送:write即可;
- socketChannel端
- 一次性連線
- 非阻塞模式 (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();
}