Невозможно установить интереса SelectionKey в отдельном потоке - PullRequest
0 голосов
/ 21 апреля 2020

Я пытаюсь создать простой сервер с использованием NIO с выбираемыми каналами и перенести все «тяжелые» логи c за пределы основного NIO l oop в отдельный поток. Но я не могу зарегистрировать SelectionKey из другого потока. Извините за длинное чтение.

Сервер запущен как обычно:

ServerSocketChannel serverChannel;
Selector selector;
try {
   serverChannel = ServerSocketChannel.open();
   ServerSocket ss = serverChannel.socket();
   InetSocketAddress address = new InetSocketAddress(port);
   ss.bind(address);
   serverChannel.configureBlocking(false);
   selector = Selector.open();
   serverChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException ex) {
   ex.printStackTrace();
   return;
}

Затем идет основной l oop, где на этапе принятия (key.isAcceptable ()) я выполняю принять ( Я предпочел бы принять соединение в отдельном потоке, но кажется, что без принятия в основном NIO l oop я не получу объект SocketChannel):

ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel sChann = server.accept();

, а затем я передаю текущий SocketChannel и SelectionKey ко 2-му потоку, чтобы сделать некоторые проверки и решить, должен ли канал быть закрыт, или я могу читать данные из канала. Если все проверки успешно пройдены, я пытаюсь зарегистрировать флаг OP_READ для этого ключа и получить следующие проблемы:

В руководстве Java написано, что SelectionKey является постоянным для канала. Однако, когда во 2-м потоке я пытаюсь выполнить

key.interestOps(SelectionKey.OP_READ);

, я получил следующее исключение:

Exception in thread "Thread-0" java.lang.IllegalArgumentException
    at java.base/sun.nio.ch.SelectionKeyImpl.interestOps(SelectionKeyImpl.java:98)
    at ConnectionAcceptor.run(ConnectionAcceptor.java:55)
    at java.base/java.lang.Thread.run(Thread.java:834)

Я читал об этом исключении в руководстве

IllegalArgumentException - Если бит в наборе не соответствует операции, которая поддерживается каналом этого ключа, то есть, если (ops & ~ channel (). ValidOps ())! = 0

и добавил несколько проверок, чтобы проверить, не в моем ли это дело. Проверки во 2-м потоке:

System.out.println("ConnectionAcceptor: valid options " + ci.sockChan.validOps());
System.out.println("ConnectionAcceptor: OP_ACCEPT " + SelectionKey.OP_ACCEPT);
System.out.println("ConnectionAcceptor: OP_READ " + SelectionKey.OP_READ);
System.out.println("ConnectionAcceptor: OP_WRITE " + SelectionKey.OP_WRITE);

результат:

ConnectionAcceptor: valid options 13
ConnectionAcceptor: OP_ACCEPT 16
ConnectionAcceptor: OP_READ 1
ConnectionAcceptor: OP_WRITE 4

, поэтому правило из руководства не нарушено и исключение IllegalArgumentException не следует создавать.

Здесь Я нашел другой способ установить требуемый флаг:

sockChan.keyFor(selector).interestOps(SelectionKey.OP_READ);

, но, используя его во втором потоке, я получаю

Exception in thread "Thread-0" java.lang.NullPointerException
    at ConnectionAcceptor.run(ConnectionAcceptor.java:59)
    at java.base/java.lang.Thread.run(Thread.java:834)

В результате он Похоже, что в то время как объекты канала и ключа были переданы во второй поток, основной NIO l oop выполнил некоторые итерации, и SelectionKey канала стал недействительным. Пожалуйста, помогите мне найти способ регистрации флага селектора канала из 2-го потока.

1 Ответ

0 голосов
/ 21 апреля 2020

С помощью

key.interestOps(SelectionKey.OP_READ);

вы пытаетесь изменить набор процентов для регистрации с

/* SelectionKey key = */ serverChannel.register(selector, SelectionKey.OP_ACCEPT);

, что ServerSocketChannel поддерживает только OP_ACCEPT.

Что вы хотите сделать, это зарегистрировать новый OP_READ на вновь принятом канале сокетов

SelectionKey aNewKey = sChann.register(selector, SelectionKey.OP_READ);

Этот новый ключ будет управлять операциями на этом сокете.


Ваша вторая попытка не удалась, потому что ваш SocketChannel не был зарегистрирован ни для какого Selector и поэтому keyFor вернул null.

...