Селектор в NIO

Java

Селектор в NIO

в предыдущем постеNIO в JAVA идет глубжеМы научились пользоватьсяBuffer, пока вIO и NIO в Javaмы немного знаемChannelконцепция, мы знаемChannelКак рельсы в шахте,BufferПодобно вагонетке на рельсах, реальная работа с данными предназначена дляBufferоперация. И в NIO есть еще одна очень важная концепция:Selector, это как система планирования в шахте.

Зачем нужен селектор

Чтобы понять, зачем существует Selector? Для ответа на этот вопрос мы должны сначала знать, что в системах UNIX существует пять моделей ввода-вывода: синхронный блокирующий ввод-вывод, синхронный неблокирующий ввод-вывод, мультиплексирование ввода-вывода, управляемый сигналом ввод-вывод и асинхронный ввод-вывод. . Что означают эти модели ввода/вывода?Давайте воспользуемся аналогией.

  • Блокирующая модель ввода-вывода: человек ловит рыбу, и когда рыба не попалась на крючок, он сидит на берегу и ждет.
  • Модель с неблокирующим вводом-выводом: играйте со своим телефоном во время рыбалки, проверяйте, не попалась ли рыба на крючок, и быстро тяните удочку, если она есть.
  • Модель мультиплексирования ввода-вывода: положите кучу удочек, продолжайте охранять кучу удочек на берегу и играйте с мобильными телефонами, когда рыба не на крючке.
  • Модель ввода-вывода с сигнальным управлением: к удочке прикреплен колокольчик. Когда он звонит, вы знаете, что рыба на крючке, и тогда вы можете сосредоточиться на игре со своим телефоном.
  • Модель асинхронного ввода-вывода: наймите человека, чтобы он ловил для меня рыбу, а затем отвез ее ко мне домой, что мне делать?

Блокировка и неблокировка относятся к тому, возвращается ли приложение немедленно или ждет, когда инициируется операция ввода-вывода. Синхронизация и асинхронность относятся к тому, инициируется ли копирование данных из пространства ядра в пространство приложения ядром или инициируется приложением, когда приложение взаимодействует с ядром.

И так называемыйВвод-вывод — это процесс копирования данных между памятью компьютера и внешними устройствами., мы знаем, что ЦП обращается к памяти намного быстрее, чем внешнее устройство, поэтому ЦП обычно сначала считывает данные внешнего устройства в память, а затем обрабатывает их. Затем в это время есть сцена, когда ваша пользовательская программа отправляет команду чтения на внешнее устройство через ЦП, для передачи данных с внешнего устройства в память требуется некоторое время, поэтому ЦП отдыхает в это время. время? Или отдать кому-нибудь? Все еще продолжайте спрашивать, вы уже прибыли? Достиг? Достиг……? Это проблема, которую должна решить модель ввода-вывода.

А модель ввода-вывода в нашей симуляции NIO — это модель мультиплексирования ввода-вывода. только заблокировавSelectorэта ветка черезSelectorпостоянные запросыChannelсостояние, тем самым достигая потока управленияSelector, в то время какSelectorконтролировать несколькоChannelцель. Вот как это представлено графически.

Использование селектора

По картинке выше мы можем догадаться, чтоSelectorкак пользоваться

Создать селектор

позвонивSelector.open()способ создания селектора.

Selector selector = Selector.open();

Создайте нужный канал

Мы знаем, что Каналы в NIO делятся на четыре типа.

  • FileChannel: файловый канал
  • DatagramChannel: Чтение данных из сети по UDP
  • SocketChannel: Чтение данных из сети через TCP
  • ServerSocketChannel: может прослушивать входящие соединения, для каждого входящего соединения будет созданSocketChannel

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

ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.socket().bind(new InetSocketAddress(8080));
//设置为非阻塞模式
socketChannel.configureBlocking(false);

Зарегистрируйте созданный канал в селекторе

Для удобстваSelectorуправлятьChannel,мы будемChannelзарегистрироваться вSelectorначальство.

//将Channel注册到Selector上
SelectionKey selectionKey = socketChannel.register(selector,SelectionKey.OP_READ);

Мы видим, что первый параметр является нашим собственнымSelector, а второй параметр — выбор типа события для мониторинга, их четыре.

  • SelectionKey.OP_CONNECT: Событие соединения продолжается, указывающее, что сервер прослушал клиентское соединение, и сервер может принять соединение.
  • SelectionKey.OP_ACCEPT: событие готовности соединения, сервер получает запрос на соединение от клиента, срабатывает
  • SelectionKey.OP_READ: Событие готовности к чтению, указывающее, что в канале уже есть данные для чтения и можно выполнить операцию чтения.
  • SelectionKey.OP_WRITE: Событие записи, указывающее, что данные могут быть записаны в канал.

ServerSocketChannelДопустимое событие дляOP_ACCEPT,SocketChannelДопустимое событие дляOP_CONNECT,OP_READ,OP_WRITE

Селектор проходит через каждый канал

На предыдущем шаге имеемChannelзарегистрированоSelector, то теперь мы можем вызватьSelector.select()Метод проходит, чтобы получить готовыйChannel.


Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> 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();метод, чтобы удалить его, потому чтоselect()метод просто получает все готовоChannelЕсли его не удалить, соответствующее событие все равно будет вызываться при следующем обходе.

полный пример

Сделайте простую программу мониторинга сервера. Контролируйте порт 8080 машины и распечатывайте отправленные данные.


public class TestNIO {

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        socketChannel.socket().bind(new InetSocketAddress(8080));
        //设置为非阻塞模式
        socketChannel.configureBlocking(false);
        //将Channel注册到Selector上
        socketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            int readyChannel = selector.select();
            if (readyChannel == 0){
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()){
                SelectionKey key = keyIterator.next();
                keyIterator.remove();
                if (key.isAcceptable()){
                    System.out.println("isAcceptable");
                    SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
                    clientChannel.configureBlocking(false);
                    clientChannel.register(key.selector(),SelectionKey.OP_READ);
                }
                else if (key.isConnectable()){
                    System.out.println("isConnectable");
                }
                else if (key.isReadable()){
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    clientChannel.read(byteBuffer);
                    System.out.println(new String(byteBuffer.array()));
                }else if (key.isWritable()){
                    System.out.println("isWritable");
                }
            }
        }
    }
}

На этом этапе вы можете использовать команду в консолиtelnet localhost 8080для подключения к серверу.

Справочная статья