NIO SelectionKey.cancel只对下一次select()生效?

貌似SelectionKey.cancel只对下一次的Selector.cancel()有效,代码如下,只贴了简要的:

nKeys = selector.select();
ExecutorService exec = Executors.newFixedThreadPool(10);
if (nKeys > 0)
{
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext())
{
SelectionKey selectionKey = (SelectionKey) it.next();
it.remove();
if (selectionKey.isAcceptable())
{
。。。 //省略了accept()通道的注册代码
}
else if(selectionKey.isReadable())
{
exec.execute(new ProcessThread(selectionKey)); //如果客户端数据没有处理完整,将会重新注册该key
selectionKey.cancel();
}
}
}

客户端只连了一次,在ProcessThread(selectionKey).run的处理过程中,select()还有两次成功返回,第一次返回时nKeys=0,第二次nKey=1。如果使用单步调试,也就是在exec.execute(new ProcessThread(selectionKey))和下一次select()之间有足够的时间处理通道的数据,则select()将阻塞。
不太明白为什么selectionKey.cancel()不起作用。

我们只是创建了一个selectedKey

第一次注册accepte

在select()后,返回 1,因为含有一个selectedKey

在handle accepte 里面,key.insertOtps(read)

在selcte()后,返回1,因为只有一个selectedKey

这个时候进入isReadable
在这个时候cancel()

那么第三次select()后,就是0了。。因为没有selectedKey了

[quote]貌似SelectionKey.cancel只对下一次的Selector.cancel()有效[/quote]是对下一次select()有效

简单的说
cancel()方法是永久的注销SelectionKey.OPxxxx,并将其放入selector的canceled set中。在下一次调用select()方法的时候,这些键会从该选择器的所有键集中移除,它关联的信道也不在监听了(除非它又重新注册)

此外,你用 exec.execute 这种含有异步的单步调试不一定准确

是的。 这是java的一个设计, 就是已经有的事件, 一定要处理掉的。

[quote]你说“它关联的信道也不在监听了(除非它又重新注册)”,但为什么我在一次select()返回0后,又多一次select()返回1,在这个时间里面我没有重新注册这个Key[/quote]

返回2个1不奇怪,你可能把select()的返回与SelectedKey.op值弄混了。

select()返回的是[color=red]一组键[/color]

你可以在isReadable下面打印东西看看,看有没有问题。

此外,你的多线程还是有问题

如果你贴出来的代码在整个的while循环里,希望你将
[code="java"]ExecutorService exec = Executors.newFixedThreadPool(10);
[/code]放在class variable,不是放在方法里面,否则有内存泄露

[quote]我的感觉是“只对下一次select有效”,你说的是“对下一次select()有效”。[/quote]

在每次选择操作期间,都可以将键添加到选择器的已选择键集以及从中将其移除,并且可以从其键集和已取消键集中将其移除。选择是由 select()、select(long) 和 selectNow() 方法执行的,执行涉及三个步骤:

1.将已取消键集中的每个键从所有键集中移除(如果该键是键集的成员),并注销其通道。此步骤使已取消键集成为空集。
2.在开始进行选择操作时,应查询基础操作系统来更新每个剩余通道的准备就绪信息,以执行由其键的相关集合所标识的任意操作。对于已为至少一个这样的操作准备就绪的通道,执行以下两种操作之一:
a.如果该通道的键尚未在已选择键集中,则将其添加到该集合中,并修改其准备就绪操作集,以准确地标识那些通道现在已报告为之准备就绪的操作。丢弃准备就绪操作集中以前记录的所有准备就绪信息。
b. 如果该通道的键已经在已选择键集中,则修改其准备就绪操作集,以准确地标识所有通道已报告为之准备就绪的新操作。保留准备就绪操作集以前记录的所有准备就绪信息;换句话说,基础系统所返回的准备就绪操作集是和该键的当前准备就绪操作集按位分开 (bitwise-disjoined) 的。
3.如果在此步骤开始时键集中的所有键都有空的相关集合,则不会更新已选择键集和任意键的准备就绪操作集。
如果在步骤2的执行过程中要将任意键添加到已取消键集中,则处理过程如步骤1。

selectionKey.cancel();
放在
exec.execute(new ProcessThread(selectionKey));

前面

你看看你多线程里面有没有
key.interestOps(SelectionKey.OP_READ)这样类似的操作,你先注释掉。。

不好说,不过我觉得不是一个bug,我也不知道你到底注册了多少个SelectedKey

我用的是最经典的nio例子,打印的结果就是这样。。。
[code="java"]
while (true) {

        if (selector.select(TIMEOUT) == 0) {
            System.out.println(".");
            continue;
        }
        Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
        while (keyIter.hasNext()) {
            int x = 0;
            SelectionKey key = keyIter.next();

            if (key.isAcceptable()) {
                System.out.println(" ... is acceptable ...");
                protocol.handleAccept(key);

            } else if (key.isReadable()) {
                System.out.println(" ... is readable ...");
                protocol.handleRead(key);
                key.cancel();

                System.out.println(" ... cancel ... ");
            } else if (key.isWritable() && key.isValid()) {
                System.out.println(" --- isWritable");
                protocol.handleWrite(key);
            }
            // 由于 select() 操作只是向 Selector 所关联的键集合中添加元素
            // 因此,如果不移除每个处理过的键,
            // 它就会在下次调用 select() 方法时仍然保留在集合中
            // 而且可能会有无用的操作来调用它。
            keyIter.remove();
        }

    }

[/code]

[code="java"]
.
.
.
.
... is acceptable ...
... is readable ...
... cancel ...
.
.
.
.

[/code]