java.nio學習筆記

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

相關文章
相關標籤/搜索