在编写一些测试代码时,我发现 Selector.select() 可以返回,而 Selector.selectedKeys() 不包含任何要处理的键。当我用
注册一个接受()ed通道时,这是在一个紧密的循环中发生的SelectionKey.OP_READ | SelectionKey.OP_CONNECT
作为感兴趣的操作。
根据文档, select() 应该在以下情况下返回:
1) 有可以采取行动的渠道。
2)您显式调用 Selector.wakeup() - 没有选择任何键。
3)您明确 Thread.interrupt() 执行 select() 的线程 - 没有选择任何键。
如果我在 select() 之后没有得到任何键,我一定是在情况 (2) 和 (3) 中。但是,我的代码没有调用wakeup()或interrupt()来启动这些返回。
关于导致这种行为的原因有什么想法吗?
简短回答:从您对接受的连接感兴趣的操作列表中删除
OP_CONNECT
——接受的连接已经连接。
我成功重现了该问题,这可能正是您所遇到的情况:
import java.net.*;
import java.nio.channels.*;
public class MyNioServer {
public static void main(String[] params) throws Exception {
final ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(true);
serverChannel.socket().bind(new InetSocketAddress("localhost", 12345));
System.out.println("Listening for incoming connections");
final SocketChannel clientChannel = serverChannel.accept();
System.out.println("Accepted connection: " + clientChannel);
final Selector selector = Selector.open();
clientChannel.configureBlocking(false);
final SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
System.out.println("Selecting...");
System.out.println(selector.select());
System.out.println(selector.selectedKeys().size());
System.out.println(clientKey.readyOps());
}
}
上述服务器收到连接后,连接上的第一个
select()
会无阻塞地退出,并且没有任何键进行就绪操作。我不知道为什么 Java 会这样,但似乎很多人都被这种行为所困扰。
Windows XP 上的 Sun JVM 1.5.0_06 以及 Linux 2.6 上的 Sun JVM 1.5.0_05 和 1.4.2_04 的结果相同。
原因是
OP_CONNECT
和 OP_WRITE
本质上是同一件事,因此您永远不应该同时注册两者(同上 OP_ACCEPT
和 OP_READ
),并且您永远不应该注册 OP_CONNECT
当通道已经连接时,就像在本例中一样,已被接受。
并且
OP_WRITE
几乎总是准备就绪,除非内核中的套接字发送缓冲区已满,所以您应该只在获得零长度写入后进行注册。因此,通过注册已连接的 OP_CONNECT,
通道,您实际上是在注册已准备就绪的 OP_WRITE,
,因此 select()
被触发。
更新:根据 @user207421 建议添加了 client.finishConnect()
您应该在连接到服务器时使用OP_CONNECT
,而不是在侦听传入连接时使用。还要确保在连接之前配置阻塞:
Selector selector = Selector.open();
SocketChannel serverChannel = SocketChannel.open(StandardProtocolFamily.INET);
serverChannel.configureBlocking(false);
serverChannel.connect(new InetSocketAddress("localhost", 5454));
serverChannel.register(selector, SelectionKey.OP_CONNECT);
// event process cycle
{
int count = selector.select();
for (SelectionKey key : selector.selectedKeys()) {
log.info(" {}", key.readyOps());
if (key.isConnectable()) {
boolean connected = client.finishConnect();
log.info("Connection is ready: {}", connected);
if (connected) {
key.interestOps(SelectionKey.OP_READ);
}
}
if (key.isReadable()) {
// read data here
}
}