Java NIO select() возвращает без выбранных ключей – почему?
Вопрос
При написании тестового кода я обнаружил, что Selector.select() может возвращать значение без Selector.selectedKeys(), содержащего какие-либо ключи для обработки.Это происходит в тесном цикле, когда я регистрирую канал Accept() с помощью
SelectionKey.OP_READ | SelectionKey.OP_CONNECT
как интересующие операции.
Согласно документации, select() должен вернуться, когда:
1) Есть каналы, на которые можно воздействовать.
2) Вы явно вызываете Selector.wakeup() — ключи не выбраны.
3) Вы явно используете Thread.interrupt() для потока, выполняющего select() - никакие ключи не выбраны.
Если я не получаю ключей после select(), я должен быть в случаях (2) и (3).Однако мой код не вызывает пробуждение() или прерывание() для инициации этих возвратов.
Есть идеи относительно того, что вызывает такое поведение?
Решение
Короткий ответ:удалять 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 ведет себя таким образом, но, похоже, такое поведение задевает многих людей.
Результат одинаков для Sun JVM 1.5.0_06 в Windows XP, а также для Sun JVM 1.5.0_05 и 1.4.2_04 в Linux 2.6.
Другие советы
Причина в том, что OP_CONNECT
и OP_WRITE
под капотом это одно и то же, поэтому никогда не следует регистрироваться в обоих случаях одновременно (то же самое OP_ACCEPT
и OP_READ
), и вам никогда не следует регистрироваться на OP_CONNECT
вообще, когда канал уже подключен, как в данном случае, будучи принятым.
И OP_WRITE
почти всегда готов, за исключением случаев, когда буфер отправки сокета в ядре заполнен, поэтому вам следует регистрироваться для этого только после того, как вы получите запись нулевой длины.Таким образом, зарегистрировав уже подключенный канал для OP_CONNECT,
ты действительно регистрировался на OP_WRITE,
который был готов, так что select()
завелся.