Chinaunix首页 | 论坛 | 博客
  • 博客访问: 128348
  • 博文数量: 69
  • 博客积分: 2895
  • 博客等级: 少校
  • 技术积分: 710
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-03 18:05
文章分类

全部博文(69)

文章存档

2010年(69)

我的朋友

分类:

2010-09-15 23:19:12

nio的select()的时候,只要数据通道允许写,每次select()返回的OP_WRITE都是true。所以在nio的写数据里面,我们在每次需要写数据之前把数据放到缓冲区,并且注册OP_WRITE,对selector进行wakeup(),这样这一轮select()发现有OP_WRITE之后,将缓冲区数据写入channel,清空缓冲区,并且反注册OP_WRITE,写数据完成。

这里面需要注意的是,每个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();
}

.....

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()。

 

 

2、nio的临时selector的使用,了解grizzly的都知道,Grizzly框架有一个比较与众不同的地方在于使用临时selector注册channel进行读或者写。这个带来什么好处呢?一个是,通常我们可能将read派发到其他线程中去,如果一次没有读完,那么就得继续注册OP_READ到主selector上;注意,nio在一些平台上有个问题,就是SelectionKey.interestOps方法跟Selector.select方法会有并发冲突,产生奇怪的现象,因此,你会看到大多数的nio框架都会保证SelectionKey.interestOps跟Selector.select的调用在同一个线程;在没有读完继续注册这个场景下,免不了线程间的context switch,如果采用一个临时selector注册并读取,就可以避免这个切换开销。另外,对于write调用,通常你可能这样写:

  1. while (byteBuffer.hasRemaining()) {  
  2.   int len = socketChannel.write(byteBuffer);  
  3.   if (len < 0){  
  4.    throw new EOFException();   
  5.   }  
  6. }  

 


   在负载比较高的时候,write返回0的次数会越来越多,while循环将空耗多次导致CPU占用偏高,这个问题在win32上比较严重,同样可以采用临时selector的解决(Cindy2.x是留在队列,等待下次写)。下例是采用临时Selector进行读的例子:

  1. Selector readSelector = SelectorFactory.getSelector();  
  2.                SelectionKey tmpKey = sc.register(readSelector,  
  3.                        SelectionKey.OP_READ);  
  4.                tmpKey.interestOps(tmpKey.interestOps() | SelectionKey.OP_READ);  
  5.                int code = readSelector.select(1000);  
  6.                tmpKey.interestOps(tmpKey.interestOps()  
  7.                        & (~SelectionKey.OP_READ));  
  8.                if (code > 0) {  
  9.                    do {  
  10.                        n = sc.read(in);  
  11.                    } while (n > 0 && in.hasRemaining());  
  12.                    in.flip();  
  13.                    decode();  
  14.                    in.compact();  
  15.                }  
  16.                SelectorFactory.returnSelector(readSelector);  

 

<>      这样的方式,某种意义上可以认为是non-blocking模式下的阻塞读,在网络条件稳定的情况下(比如内网),能带来比较高的效率。

阅读(1056) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~