问题
今天调试NIO后台发现一个蛋疼的问题。经过调试之后,出现问题的流程描述如下:
客户端向服务端发送消息,服务端使用IO多路复用处理输入,即一个selector监听多个channel。第一条消息正常接收,发送第二条消息时,select()立即返回0,然后开始continue无限循环,导致第二条消息无法正常处理。
下方是服务端处理select()的代码(由一个异步线程处理)即read监听线程
while (!isClosed.get()) {
if (readSelector.select() == 0) {
//这里有一个等待操作,等待注册结束
waitSelection(inRegInput);
continue;
}
Iterator<SelectionKey> iterator = readSelector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isValid()) {
// 取消继续对keyOps的监听
key.interestOps(key.readyOps() & ~SelectionKey.OP_READ);
//线程池执行read操作
inputHandlePool.execute(new InputHandlerImpl(key));
}
}
}
按理说,有新的channel就绪时,select()会返回大于0的数,或者没有channel就绪,select()也是保持阻塞状态,不会有返回。可现在问题是,为什么它立即返回0了?
原因
在Stack Overflow上找到了答案:
Java NIO Selector select() returns 0 although channels are ready
其实select()是否返回与selectedKeys集合有关。当selectedKeys集合不为空时,select()会立即返回,但是其返回值是发生改变的keys数量,即新的就绪通道数量,这里不可能是1。因此我的这个场景下,第一条消息会产生一个新的key,我处理完没有将其删除,所以收第二条消息时,认定这个key没有发生改变,就会导致select()返回0,从而导致无限循环。所以,一定要把key从selectedKeys集合中移除。
解决方法:很简单,遍历迭代器的循环中加一句iterator.remove()移除已处理的key。代码如下:
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
......
}