nio的select()的時候,只要數據通道容許寫,每次select()返回的OP_WRITE都是true。因此在nio的寫數據裏面,咱們在每次須要寫數據以前把數據放到緩衝區,而且註冊OP_WRITE,對selector進行wakeup(),這樣這一輪select()發現有OP_WRITE以後,將緩衝區數據寫入channel,清空緩衝區,而且反註冊OP_WRITE,寫數據完成。java
這裏面須要注意的是,每一個SocketChannel只對應一個SelectionKey,也就是說,在上述的註冊和反註冊OP_WRITE的時候,不是經過channel.register()和key.cancel()作到的,而是經過key.interestOps()作到的。代碼以下:緩存
public void write(MessageSession session, ByteBuffer buffer) throws ClosedChannelException {
SelectionKey key = session.key();
if((key.interestOps() & SelectionKey.OP_WRITE) == 0) {
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
}
try {
writebuf.put(buffer);
} catch(Exception e) {
System.out.println("want put:"+buffer.remaining()+", left:"+writebuf.remaining());
e.printStackTrace();
}
selector.wakeup();
}網絡
.....session
while(true) {
selector.select();
.....
if(key.isWritable()) {
MessageSession session = (MessageSession)key.attachment();
//System.out.println("Select a write");
synchronized(session) {
writebuf.flip();
SocketChannel channel = (SocketChannel)key.channel();
int count = channel.write(writebuf);
//System.out.println("write "+count+" bytes");
writebuf.clear();
key.interestOps(SelectionKey.OP_READ);
}
}
......
}併發
要點一:不推薦直接寫channel,而是經過緩存和attachment傳入要寫的數據,改變interestOps()來寫數據;框架
要點二:每一個channel只對應一個SelectionKey,因此,只能改變interestOps(),不能register()和cancel()。socket
二、nio的臨時selector的使用,瞭解grizzly的都知道,Grizzly框架有一個比較不同凡響的地方在於使用臨時selector註冊channel進行讀或者寫。這個帶來什麼好處呢?一個是,一般咱們可能將read派發到其餘線程中去,若是一次沒有讀完,那麼就得繼續註冊OP_READ到主selector上;注意,nio在一些平臺上有個問題,就是SelectionKey.interestOps方法跟Selector.select方法會有併發衝突,產生奇怪的現象,所以,你會看到大多數的nio框架都會保證SelectionKey.interestOps跟Selector.select的調用在同一個線程;在沒有讀完繼續註冊這個場景下,免不了線程間的context switch,若是採用一個臨時selector註冊並讀取,就能夠避免這個切換開銷。另外,對於write調用,一般你可能這樣寫:spa
while (byteBuffer.hasRemaining()) { int len = socketChannel.write(byteBuffer); if (len < 0){ throw new EOFException(); } }
在負載比較高的時候,write返回0的次數會愈來愈多,while循環將空耗屢次致使CPU佔用偏高,這個問題在win32上比較嚴重,一樣能夠採用臨時selector的解決(Cindy2.x是留在隊列,等待下次寫)。下例是採用臨時Selector進行讀的例子:線程
Selector readSelector = SelectorFactory.getSelector(); SelectionKey tmpKey = sc.register(readSelector, SelectionKey.OP_READ); tmpKey.interestOps(tmpKey.interestOps() | SelectionKey.OP_READ); int code = readSelector.select(1000); tmpKey.interestOps(tmpKey.interestOps() & (~SelectionKey.OP_READ)); if (code > 0) { do { n = sc.read(in); } while (n > 0 && in.hasRemaining()); in.flip(); decode(); in.compact(); } SelectorFactory.returnSelector(readSelector);
<> 這樣的方式,某種意義上能夠認爲是non-blocking模式下的阻塞讀,在網絡條件穩定的狀況下(好比內網),能帶來比較高的效率。rest