Селектор в 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
для подключения к серверу.