Перейти к контенту

Java NIO Selector как использовать

Селектор Java NIO — это компонент, который может проверять один или несколько экземпляров Java NIO Channel и определять, какие каналы готовы, например, для чтения или записи. Таким образом, один поток может управлять несколькими каналами и, следовательно, несколькими сетевыми подключениями.

Зачем использовать селектор?

Преимущество использования одного потока для обработки нескольких каналов состоит в том, что вам нужно меньше потоков для обработки каналов. На самом деле, вы можете использовать только один поток для обработки всех ваших каналов. Переключение между потоками является дорогостоящим для операционной системы, и каждый поток также занимает некоторые ресурсы(память) в операционной системе.

 

Имейте в виду, что современные операционные системы и ЦП становятся все лучше и лучше в многозадачном режиме, поэтому издержки многопоточности со временем уменьшаются.

Вот иллюстрация потока, использующего селектор для обработки 3 каналов:

Java NIO: Selectors

Создание селектора

Вы создаете Selector, вызывая метод Selector.open(), например так:

Selector selector = Selector.open();

Регистрация каналов с помощью селектора

Чтобы использовать канал с селектором, вы должны зарегистрировать канал с помощью селектора. Это делается с помощью метода SelectableChannel.register(), например:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Канал должен находиться в неблокирующем режиме для использования с селектором. Это означает, что вы не можете использовать FileChannel с селектором, поскольку FileChannel нельзя переключить в неблокирующий режим. Сокетные каналы будут работать нормально.

Обратите внимание на второй параметр метода register(). Это «набор интересов», означающий, какие события вы хотите прослушать на канале с помощью селектора. Вы можете прослушать четыре разных события:

  • соединить
  • принимать
  • Читать
  • Написать

Канал, который «запускает событие», также называется «готовым» к этому событию. Таким образом, канал, который успешно подключился к другому серверу, «готов к подключению».

Канал сокета сервера, который принимает входящее соединение, готов к приему. Канал с данными, готовыми для чтения, готов к «чтению».

Эти четыре события представлены четырьмя константами SelectionKey:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

Если вас интересует более одного события, ИЛИ константы вместе, например:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey

Как вы видели в предыдущем разделе, когда вы регистрируете канал с помощью Selector, метод register() возвращает объекты SelectionKey. Этот объект SelectionKey содержит несколько интересных свойств:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)

Я опишу эти свойства ниже.

Набор интересов

Набор интересов — это набор событий, которые вы хотите «выбрать». Вы можете прочитать и написать этот interest set, установленный с помощью SelectionKey следующим образом:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet  SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet  SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet  SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet  SelectionKey.OP_WRITE;    

Как вы можете видеть, вы можете И установить процент с данной константой SelectionKey, чтобы узнать, входит ли определенное событие в набор.

Готовый комплект

Готовый набор — это набор операций, для которых канал готов. В первую очередь вы получите доступ к готовому набору после выбора. Выбор объясняется в следующем разделе.

int readySet = selectionKey.readyOps();
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

Канал + Селектор

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();    

Прикрепление объектов

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

Выбор каналов с помощью селектора

Как только вы зарегистрируете один или несколько каналов с помощью селектора, вы можете вызвать один из методов select(). Эти методы возвращают каналы, которые «готовы» для интересующих вас событий(подключение, принятие, чтение или запись). Другими словами, если вас интересуют каналы, готовые к чтению, вы получите каналы, готовые к чтению, из методов select().

Вот методы select():

  • int select()
  • int select(длительное время ожидания)
  • int selectNow()

select() блокирует, пока хотя бы один канал не будет готов к событиям, для которых вы зарегистрировались.

select(long timeout) делает то же самое, что и select(), за исключением того, что он блокируется на максимальное время ожидания в миллисекундах(параметр).

selectNow() вообще не блокируется. Он сразу возвращается с любыми готовыми каналами.

Значение int, возвращаемое методами select(), сообщает, сколько каналов готовы. То есть сколько каналов стало готово с тех пор, как вы в последний раз вызывали select(). Если вы вызываете select(), и он возвращает 1, потому что один канал стал готовым, и вы вызываете select() еще раз, и еще один канал стал готовым, он вернет 1 снова.

Если вы ничего не сделали с первым каналом, который был готов, у вас теперь есть 2 канала готовности, но только один канал стал готовым между каждым вызовом select().

selectedKeys()

Set selectedKeys = selector.selectedKeys();    

Когда вы регистрируете канал с помощью Selector, метод Channel.register() возвращает объект SelectionKey. Этот ключ показывает, что есть регистрация каналов с этим селектором. К этим ключам вы можете получить доступ через метод selectedKeySet(). Из ключа выбора.

Set selectedKeys = selector.selectedKeys();

Iterator keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {
    
    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if(key.isConnectable()) {
        // a connection was established with a remote server.

    } else if(key.isReadable()) {
        // a channel is ready for reading

    } else if(key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

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

Обратите внимание на вызов keyIterator.remove() в конце каждой итерации. Селектор не удаляет экземпляры SelectionKey из выбранного набора ключей. Вы должны сделать это, когда закончите обработку канала. В следующий раз, когда канал станет «готовым», селектор снова добавит его в выбранный набор ключей.

Канал, возвращаемый методом SelectionKey.channel(), должен быть приведен к каналу, с которым вам нужно работать, например, ServerSocketChannel или SocketChannel и т. д.

wakeup()

Поток, который вызвал метод select(), который заблокирован, можно заставить покинуть метод select(), даже если ни один канал еще не готов. Это делается с помощью другого потока, вызывающего метод Selector.wakeup() в Selector, для которого первый поток вызвал select(). Поток, ожидающий внутри select(), немедленно вернется.

Если другой поток вызывает wakeup(), и ни один из потоков в данный момент не заблокирован внутри select(), следующий поток, который вызывает select(), немедленно «проснется».

close()

Когда вы закончите работу с Selector, вы вызываете его метод close(). Это закрывает селектор и делает недействительными все экземпляры SelectionKey, зарегистрированные этим селектором. Сами каналы не закрыты.

Пример полного селектора

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);


while(true) {

  int readyChannels = selector.selectNow();

  if(readyChannels == 0) continue;


  Set selectedKeys = selector.selectedKeys();

  Iterator keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if(key.isConnectable()) {
        // a connection was established with a remote server.

    } else if(key.isReadable()) {
        // a channel is ready for reading

    } else if(key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
  }
}

Оцени статью

Средняя оценка / 5. Количество голосов:

Спасибо, помогите другим - напишите комментарий, добавьте информации к статье.

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

 

Сайдбар