NIO中几个非常重要的技术点

来源:互联网 发布:零件的加工方法与编程 编辑:程序博客网 时间:2024/05/16 09:40

这些都是在实践中踩过雷的,今天某应用再次踩雷,把遇到的几个雷都收集一下,给后来者参考。

1.即使是accept事件,没有真正的read和write,Channel也要关闭,否则unix domain socket会被泄漏(WINDOWS更可怕),因为NIO的每个

Channel上都有两个FD用来监听事件(接收和发送走不同的FD)。

2.cancel事件导致CPU占用100%,http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933 

其原因就是调用key.cancel()时底层在下一次seelect前并没有真正的取消。导致等待select事件返回却又没有返回我们注册的key.这个事件不断地

循环触发,CPU一直处理返回 key为0的select()调用。解决方法有两种,一是在key.cancel()后立即selectNow();但是如果是多线程并发操作,有

可能这两行语句中间线程被切换,使得key.cancel()后没有立即执行 selectNow().这在多Selector情况下是可能的。另一种就是jetty处理方式,如果

select()返回0且连续几次出现这样的情况(有事件触发返回,却不是返回我们注册的KEY),就将有效的key重新注册到一个新的selector上。其实

glassfish在处理多次次次次write返回为0的情况时也是这种策略。


示例代码:(真实的项目中)

            int selectTimeout = connectionConfig.getSelectTimeout();            int allProcessMaxTime = connectionConfig.getAllProcessMaxTime();            //selector在实现时有bug,epool底层可能会发送一个错误的信号导致select方法提前返回,但没有            //返回注册的事件,而且不断循环造成CPU100%            int slelectZeroCount = 0;            int maxZeroCount = 20;            int fixed = 0;            while (selector.isOpen() && selector.keys().size() != 0 && allProcessMaxTime > 0) {                long start = System.currentTimeMillis();                // 查询看是否有已经准备好的通道,指定超时时间                int count = selector.select(selectTimeout);                if (count == 0) {                    slelectZeroCount++;                } else {                    slelectZeroCount = 0;                    //保证是连续的count==0时才将slelectZeroCount++,如果其中有一次返回注册事件测已经正常                }                if (slelectZeroCount > maxZeroCount && fixed == 0) {                    //没有尝试修复动作,则先进行修复干预                    for (SelectionKey key : selector.keys()) {                        if (key.isValid() && key.interestOps() == 0) {                            key.cancel();                        }                    }                    fixed = 1;                } else if (slelectZeroCount > maxZeroCount && fixed == 1) {                    //如果已经干预过仍然连续返回0,注意如果不返回0的话slelectZeroCount就被置0.                    //重新获取一个selector,将当前事件重新注册到新的selector上。并销毁当前selector                    Selector newSelector = this.getSelector();                    this.changeSelector(selector, newSelector);                    selector = newSelector;                }                //对channel进行正常处理          } 

重新注册的代码:

private synchronized void changeSelector(Selector oldSelector, Selector newSelector) {        for (SelectionKey key : oldSelector.keys()) {            if (!key.isValid() || key.interestOps() == 0) {                continue;            }            Object att = key.attachment();            try {                if (att == null) {                    key.channel().register(newSelector, key.interestOps());                } else {                    key.channel().register(newSelector, key.interestOps(), att);                }            } catch (ClosedChannelException e) {                SocketChannel sc = (SocketChannel) key.channel();                sc.close();            }        }        try {            oldSelector.close();        } catch (IOException e) {            logger.error(e.getMessage());        }    }

同样对于网络状态不好时,连续写操作返回0的处理:

    private void flushData(Selector selector, SocketChannel socketChannel, ByteBuffer byteBuffer)            throws IOException {        int count = 0;        int maxCount = 20;        while (byteBuffer.hasRemaining()) {            int len = socketChannel.write(byteBuffer);            if (len < 0) {                throw new EOFException("write channel is closed.");            }            // 如果不对len==0(即当前网络不可用)的情况处理,则while(byteBuffer.hasRemaining())可能一直            // 循环下去而消耗大量的CPU.            if (len == 0) {                count++;            } else {                count = 0;            }            if (count > maxCount) {                throw new IOException("can't connect to target.");            }        }    }