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

相关文章
相关标签/搜索