Netty Journey: очки знаний NIO, которые вам нужны, уже здесь!

Netty

NIO思维导图.png

Оригинальная HD-карта разума (xmind/pdf/jpg) можете подписаться на официальный аккаунт:一枝花算不算浪漫ОтвечатьnioВот и все.

предисловие

Прошу прощения, что давно не обновлял оригинальную статью.После прочтения последнего времени обновления, оно затянулось больше чем на месяц.

Я учился все это времяNettyСоответствующее знание, потому что здесь задействовано много точек знаний, оно также потребовало много обходных путей. В настоящее время чистые учебные материалы в Интернете ослепительны.Постепенный способ разделения общего количества не только для того, чтобы увидеть лес, но и для того, чтобы увидеть растительность.

Раньше я был с другом в Ханчжоу.СяофэйТакже было упомянуто, что первоначальные намерения обоих в этом отношении одинаковы, и я надеюсь, что больше друзей смогут присоединиться к совместному изучению и обсуждению.(PS: эта статья изучена и организована вместе с Xiaofei~)

Nettyпредставляет собой асинхронную, управляемую событиями структуру веб-приложений и инструмент, основанный наNIOСреды клиентского и серверного программирования. Итак, мы начинаем сNIOПроанализируйте и объясните основную основу, связанную с зависимостями, чтобы служитьNettyНачало познавательного пути.

Обзор основ сетевого программирования

1. Socket

SocketОн имеет значение самого «сокета», что не является уникальным понятием в Java, а является независимым от языка стандартом.Любой язык программирования, который может реализовать сетевое программирование, имеетSocket. существуетLinuxВ среде специальный тип файлов, используемый для представления сетевого взаимодействия между процессами, его сущность представляет собой псевдофайл, формируемый ядром с помощью буферов. Поскольку это файл, конечно, мы можем использовать файловый дескриптор для ссылки на сокет.

Подобно трубам,LinuxЦелью системы инкапсулировать его в файл является унификация интерфейса, чтобы операции чтения и записи сокетов и чтения и записи файлов были согласованы. Разница в том, что каналы в основном используются для локального межпроцессного взаимодействия, а сокеты в основном используются для передачи данных между сетевыми процессами.

Это можно понять так:SocketДва приложения на этой сетевой связи для обмена данными через интерфейс программирования API двунаправленной связи.

SocketКонкретные этапы основного потока коммуникации следующие:

(1) Через серверListenВключите мониторинг и дождитесь доступа клиента.

(2) Сокет клиента проходит черезConnectПодключиться к сокету на стороне сервера, сторона сервера проходитAcceptПолучение клиентских подключений. существуетconnect-acceptВо время процесса операционная система выполнит трехстороннее рукопожатие.

(3) Клиент и сервер черезwriteа такжеreadОтправляйте и получайте данные, ОС завершитTCPПодтверждение данных, повторная передача и другие шаги.

(4) пройтиcloseЧтобы закрыть соединение, операционная система помашет четыре раза.

Для языка программирования Java,java.netПакеты — это фундаментальные библиотеки классов для сетевого программирования. вServerSocketа такжеSocketЭто основной тип сетевого программирования.

SeverSocketТип серверного приложения.Socketтип установленного соединения. Когда соединение установлено успешно, и сервер, и клиент будут иметьSocketПример объекта, вы можете передать этоSocketПример объекта, который завершает все операции сеанса. Для полного подключения к сетиSocketравны, случай оценки сервер-клиент отсутствует.

2. Введение в модель IO

Для операции ввода-вывода данные будут сначала скопированы в пространство ядра, а затем скопированы из пространства ядра в пространство пользователя, поэтому один разreadОперация будет проходить в два этапа:

(1) Ожидание подготовки данных

(2) Скопируйте данные из пространства ядра в пространство пользователя

На основе двух вышеуказанных этапов генерируются пять различных режимов ввода-вывода.

  1. Блокировка ввода-вывода: подчиненный процесс инициирует операцию ввода-вывода и ожидает завершения двух вышеуказанных фаз, в это время эти две фазы блокируются вместе.
  2. Неблокировка IO: процесс продолжает задавать, готов ли IO, а затем инициирует операцию чтения, когда она готова, а затем копирует данные из пространства ядра в пространство пользователя. Первый этап не блокирует, но опросы, второй этап блоки.
  3. Мультиплексирование ввода-вывода: несколько подключений используют один и тот же выбор, чтобы спросить, готов ли ввод-вывод.Если он готов, он вернет, что данные готовы, а затем соответствующее соединение инициирует операцию чтения для передачи данных из ядра. скопировано в пространство пользователя. Двухступенчатая раздельная блокировка.
  4. Управляемый сигналом ввод-вывод: когда процесс инициирует операцию чтения, он немедленно возвращается.Когда данные будут готовы, он уведомит процесс в форме уведомления, и процесс инициирует операцию чтения, чтобы скопировать данные из пространство ядра в пространство пользователя. Первый этап не блокирует, второй этап блокирует.
  5. Асинхронный ввод-вывод: когда процесс инициирует операцию чтения, он немедленно возвращается.После того, как данные будут готовы и скопированы в пользовательское пространство, процесс будет уведомлен о получении данных. Ни одна сцена не блокирует.

Эти пять режимов ввода-вывода нетрудно найти по двум парам отношений: синхронные и асинхронные, блокирующие и неблокирующие. Итак, чтобы немного объяснить:

синхронный и асинхронный

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

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

блокирующий и неблокирующий

  • блокировать:Блокировка заключается в инициировании запроса, а вызывающий объект ожидает возврата результата запроса, то есть текущий поток будет приостановлен, не сможет заниматься другими задачами и сможет продолжить работу только тогда, когда будут готовы условия.
  • Неблокирующий:Неблокирующий — это инициация запроса, вызывающему абоненту не нужно ждать возврата результата, и он может сначала заняться другими делами.

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

Если объединенные блоки синхронизации (blocking-IO) короткоBIO, синхронный неблокирующий (non-blocking-IO) короткоNIOи асинхронный неблокирующий (asynchronous-non-blocking-IO) короткоAIOЧто это значит?

  • BIO(Режим синхронного блокирующего ввода-вывода): Чтение и запись данных должны быть заблокированы в потоке, ожидающем его завершения. Здесь используется классический пример с кипящей водой. Предположим, что есть сцена с кипящей водой. Есть ряд чайников с кипящей водой. Режим работы БИО заключается в том, чтобы попросить нить оставаться в чайнике, пока чайник не закипит, прежде чем перейти к следующий чайник. Но на самом деле поток ничего не делает, ожидая, пока закипит чайник.
  • NIO(Синхронный неблокирующий): Поддерживаются как блокирующий, так и неблокирующий режимы, но здесь мы проиллюстрируем его синхронным неблокирующим режимом ввода-вывода, так что же такое синхронный неблокирующий режим? Если вы также возьмете кипящую воду в качестве примера, подход NIO состоит в том, чтобы попросить поток постоянно опрашивать состояние каждого чайника, чтобы увидеть, изменилось ли состояние любого чайника, чтобы перейти к следующему шагу.
  • AIO(Модель асинхронного неблокирующего ввода-вывода): в чем разница между асинхронным неблокирующим и синхронным неблокирующим? Асинхронная неблокировка не требует, чтобы поток опрашивал изменения состояния всех операций ввода-вывода.После соответствующего изменения состояния система уведомит соответствующий поток для обработки. На каждом чайнике установлен переключатель, соответствующий кипячению воды.После того, как вода закипит, чайник автоматически оповестит меня о том, что вода закипела.

javaсерединаBIO,NIOа такжеAIOпонял как даJava 语言На уровне операционной системы эти триIOИнкапсуляция модели. Когда программисты используют эти инкапсулированные API, им не нужно заботиться о знании операционной системы и писать разные коды для разных операционных систем.JavaAPI на нем. Поэтому, чтобы дать читателям более конкретное и рекурсивное понимание этих трех моделей иNIOЕсть явный контраст, который продолжается ниже.

Java BIO

BIOМетод программирования обычно является древним продуктом Java, существовавшим со времен JDK 1.0-JDK1.4. Процесс реализации программирования таков: сначала запуститеServerSocketДля прослушивания сетевых запросов клиент запускаетSocketИнициировать сетевой запрос, по умолчаниюSeverSocketБудет создан поток для обработки запроса, и если на сервере нет ни одного потока, клиент будет заблокирован в ожидании или будет отклонен. Режим реализации сервера — одно соединение и один поток, то есть, когда у клиента есть запрос на соединение, серверу необходимо запустить поток для обработки. Общая структура выглядит следующим образом:

aJ2i9K.png

Если вы хотите, чтобыBIOКоммуникационная модель способна обрабатывать несколько клиентских запросов одновременно, поэтому необходимо использовать многопоточность (главным образом потому, чтоsocket.accept(),socket.read(),socket.write()Три основные задействованные функции синхронно заблокированы), то есть он создает новый поток для каждого клиента для обработки ссылки после получения запроса на подключение клиента. После завершения обработки он возвращает ответ клиенту, поток уничтожен . Это типичная модель связи запрос-ответ. Мы можем представить ненужные накладные расходы на потоки, если это соединение ничего не делает, но это можно сделать с помощьюмеханизм пула потоковУлучшенные пулы потоков также могут сделать создание и повторное использование потоков относительно дешевыми. После использования механизма пула потоков для улучшенияBIOСхема модели выглядит следующим образом:

aJ2NEn.png

BIOЭтот метод подходит для архитектуры с относительно небольшим количеством соединений и фиксированным числом соединений.Этот метод имеет относительно высокие требования к ресурсам сервера, а параллелизм ограничен приложениями.Это единственный выбор перед JDK1.4, но программа интуитивно понятна и проста в понимании.Java BIOВ интернете много примеров программирования, поэтому я не буду приводить здесь примеры кодирования, в конце концов, позжеNIOВ этом-то и дело.

Java NIO

NIO(Новый ввод-вывод или ввод-вывод без блокировки), представленный начиная с JDK1.4.非阻塞IO, это非阻塞+ 同步режим связи. здесьNo Blocking IOиспользуется для различения вышеперечисленныхBIO.

NIOхочу решитьBIOпроблемы параллелизма, черезReactor模式событийный механизм для достиженияNon Blockingиз. когдаsocketЕсть потоки, доступные для чтения или записиsocketОперационная система уведомит приложение о соответствующей обработке, и приложение будет считывать поток в буфер или записывать в операционную систему.

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

Когда соединение создано, ему не нужно соответствовать потоку, соединение будет зарегистрировано в多路复用器выше, так что все соединения могут быть выполнены только с одним потоком, когда多路复用器При опросе, если есть запрос на соединение, для обработки открывается только один поток, то есть один запрос на режим потока.

NIOобеспечивает то же самоеSocketа такжеServerSocketСоответствующийSocketChannelа такжеServerSocketChannelДве разные реализации канала сокета, как показано в следующей структуре. участие здесьReactorШаблоны проектирования, мультиплексированиеSelector,BufferНе беспокойтесь об этом пока, я расскажу об этом позже.

aJ2a40.png

Метод NIO подходит для архитектур с большим количеством соединений и относительно короткими соединениями (легкая операция), таких как чат-серверы, где параллелизм ограничен приложениями, а программирование сложно, JDK1.4 начал его поддерживать. в то же время,NIOОтличие от обычного IO можно в основном отличить от носителя хранения данных, заблокирован ли он и т.д.:

NIO和普通IO区别.png

Java AIO

а такжеNIOОтличие в том, что при выполнении операций чтения и записи нужно только напрямую вызывать APIreadилиwriteметод. Оба метода являются асинхронными.Для операций чтения, когда есть поток для чтения, операционная система будет передавать доступный для чтения поток.readметод и уведомить приложение; для операций записи, когда операционная системаwriteОперационная система активно уведомляет приложение, когда поток, переданный методом, был записан. То есть его можно понимать какread/writeВсе методы являются асинхронными, и функция обратного вызова будет активно вызываться после завершения. существуетJDK7, предоставляет реализации асинхронных файловых каналов и асинхронных каналов сокетов, которые называютсяNIO.

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

насколько я могу судитьAIOПрименение не очень широкое,NettyПробовал это раньшеAIO, но сдался.

2. Введение основных компонентов NIO

1. Channel

существуетNIO, Все основные операции ввода-выводаChannelначал,Channelпройти черезBuffer(缓冲区)Выполнять операции чтения и записи.

read()Указывает на чтение данных в канале в буфер,write()Указывает, что данные буфера записываются в канал.

Channel和Buffer互相操作.png

ChannelСуществует много классов реализации, вот три наиболее часто используемых:

  • SocketChannel: Канал, по которому клиент инициирует TCP-соединение.
  • ServerSocketChannel: сервер прослушивает TCP-канал нового соединения. Для каждого нового клиентского соединения будет установлен соответствующий SocketChannel.
  • FileChannel: Чтение и запись данных из файла

вSocketChannelа такжеServerSocketChannelОн чаще всего используется в сетевом программировании, и конкретное использование будет объяснено в окончательном примере кода через некоторое время.

2. Buffer

концепция

BufferТакже известный как буфер памяти, который по сути является блоком памяти, мы можем записывать данные в эту память, а затем читать данные из этой памяти. Эта память также может быть инкапсулирована какNIO Bufferобъект и предоставляет набор часто используемых методов, облегчающих нам операции чтения и записи в этом блоке памяти.

Bufferсуществуетjava.nioопределяется как абстрактный класс в:

Buffer结构体系.png

мы можем поставитьBufferЭто понимается как инкапсуляция массива, наша наиболее часто используемаяByteBufferСоответствующая структура данныхbyte[]

Атрибуты

BufferЕсть 4 очень важных свойства:вместимость, предел, положение, отметка

Buffer中基本属性.png

  • capacityАтрибут: емкость, максимальное значение элементов данных, которое может содержать буфер, назначается при инициализации и создании буфера и не может быть изменено.

capacity.png

На приведенном выше рисунке емкость инициализированного буфера равна 8 (от 0 до 7 на рисунке, всего 8 элементов), поэтомуcapacity = 8

  • limitАтрибут: представляет верхний предел чтения и записи буфера.

    • В режиме записи:limitПредставляет верхнее предельное положение, в которое можно записывать данные, на этот разlimit = capacity

    В режиме чтения: вBufferПосле записи всех данных вызовомflip()метод, переключитесь в режим чтения, на этот разlimitравныйBufferРазмер фактически записанных данных. потому чтоBufferможет быть не заполнено, поэтомуlimit<=capacity

  • positionАтрибут: представляет чтение или записьBufferпозиция. По умолчанию 0.

    • В режиме записи: каждыйBufferнапишите значение,positionОн автоматически увеличится на 1, представляя позицию следующей записи.
    • В режиме чтения: каждыйBufferсчитывает значение,positionОн автоматически добавит 1 для представления позиции следующего чтения.

NIO属性概念.png

Из приведенного рисунка хорошо видно, что в режиме чтения и записиемкость, предел, положениеотношение.

  • markАтрибут: представляет метку.С помощью метода mark() текущее значение позиции записывается, и значение позиции присваивается метке.В последующем процессе записи или чтения текущая позиция может быть восстановлена ​​методом reset() , Значение фиксируется меткой.

С этими важными атрибутами покончено, можно еще раз вспомнить:

0 <= mark <= position <= limit <= capacity

Отношения между этими атрибутами теперь должны быть ясны~

Буферизация общих операций

Создать буфер
  • allocate(int capacity)
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);

создан в примереByteBufferэто объект, основанный на куче памяти.

  • wrap(array)

wrapметод может обернуть массив вBufferОбъект:

ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes());
channel.write(buffer);
  • allocateDirect(int capacity)

пройти черезallocateDirectметод также может быстро создать экземплярBufferобъект иallocateочень похоже, разница вот в чемallocateDirectсоздан на основепамять вне кучиОбъект.

Память вне кучи не находится в куче JVM и не управляется сборщиком мусора. Когда память вне кучи выполняет некоторые операции ввода-вывода базовой системы, эффективность будет выше.

Операция записи в буфер

BufferПисать можно черезput()а такжеchannel.read(buffer)Пишите двумя способами.

Обычно, когда мы читаем операции NIO, мы начинаем сChannelчтение данных запись вBuffer, что соответствуетBufferизоперация записи.

Операция чтения буфера

BufferЧтение может осуществляться черезget()а такжеchannel.write(buffer)Читать двумя способами.

Опять же, мы правыBufferОперация чтения , наоборот, вернаChannelизоперация записи. читатьBufferданные, а затем написатьChannelсередина.

Channel和Buffer互相操作.png

Другие распространенные методы
  • rewind(): сбросить позицию на 0, и буфер снова может быть прочитан и записан.Как правило, этот метод подходит для операций чтения, которые можно понимать как повторное чтение буфера.
public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}
  • flip(): очень распространенный метод, обычно используемый при переключении из режима записи в режим чтения. Он также установит позицию в 0, а затем установит предел, равный исходной записанной позиции.
public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}
  • clear(): Сброс данных в буфере, этот способ в основном для режима записи, т.к. установлен лимит емкости, в режиме чтения будут проблемы.
public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}
  • mark()&reset(): mark()Метод заключается в сохранении текущегоpositionк переменнойmarkг, то поreset()метод восстановления текущегоpositionдляmark, код реализации очень прост:
public final Buffer mark() {
    mark = position;
    return this;
}

public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}

Обычно используемые методы чтения и письма можно резюмировать с помощью рисунка:

Buffer读写操作.png

3. Selector

концепция

SelectorМы часто говорим, что это один из самых важных компонентов NIO.多路复用器означаетSelectorкомпоненты.SelectorКомпонент используется для опроса одного или несколькихNIO ChannelЯвляется ли состояние доступным для чтения и записи. С помощью механизма опроса можно управлять несколькими каналами, что означает возможность управления несколькими сетевыми подключениями.

Selector原理图.png

механизм опроса

  1. Во-первых, канал должен быть зарегистрирован в селекторе, чтобы селектор знал, какими каналами нужно управлять.
  2. Затем Селектор будет непрерывно опрашивать прописанный на нем Канал.Если у определенного Канала есть время чтения или записи, Канал будет опрошен Селектором, а затем через SelectionKey можно будет получить готовую коллекцию Каналов для последующих IO-операций.

轮询机制.png

манипулирование недвижимостью

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

пройти черезopen()метод, мы можем создатьSelectorобъект.

Selector selector = Selector.open();
  1. Зарегистрировать канал в селекторе

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

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

определенныйChannelзарегистрироваться вSelector, то Канал должен бытьнеблокирующийВсе вышеупомянутый код имеетconfigureBlocking()операция конфигурации.

существуетregister(Selector selector, int interestSet)Второй параметр метода, идентифицирующийinterestКоллекции, то есть, какие события интересуют Selector, могут отслеживать четыре различных типа событий:

public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << ;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
  • Connect事件: Событие завершения соединения (TCP-соединение), только для клиентов, соответствующее SelectionKey.OP_CONNECT.
  • Accept事件: Принимать новые события подключения, применимые только к серверу, соответствующему SelectionKey.OP_ACCEPT.
  • Read事件: событие чтения, применимое к обоим концам, соответствующее SelectionKey.OP_READ , указывающее, что буфер доступен для чтения.
  • Write事件: время записи, применимое к обоим концам, соответствующее SelectionKey.OP_WRITE , указывающее, что буфер доступен для записи.

ChannelЗапускается событие, указывающее, что время готово:

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

Конечно,SelectorМожно одновременно интересоваться несколькими событиями, и мы можем использовать операцию ИЛИ для объединения нескольких событий:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

Селектор некоторых других операций

Выберите канал
public abstract int select() throws IOException;
public abstract int select(long timeout) throws IOException;
public abstract int selectNow() throws IOException;

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

select(long timeout)вselect()Механизм тайм-аута добавлен на основе .selectNow()Возврат сразу без блокировки.

Очень важно отметить одну вещь: selectметод возвращенintзначение, указывающее, сколькоChannelготов.

с момента последнего звонкаselectсколько после методаChannelБыть готовым. Если называетсяselectметод, потому что существуетChannelКогда он становится готовым, он возвращает 1;

Если вы позвоните сноваselectметод, если другойChannelКогда все будет готово, он снова вернет 1.

Получите действенный канал
Set selectedKeys = selector.selectedKeys();

Когда будут готовы новыеChannel,передачаselect()метод, ключ будет добавлен в коллекцию Set.

3. Пример кода

Впереди так много предзнаменований, в основном, чтобы все понялиNIOПример кода также удобен для того, чтобы каждый мог написать его самостоятельно в будущем.NIOПрограмма для сетевого программирования. Основные шаги для создания сервера NIO следующие:

1. 打开ServerSocketChannel,监听客户端连接
2. 绑定监听端口,设置连接为非阻塞模式
3. 创建Reactor线程,创建多路复用器并启动线程
4. 将ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件
5. Selector轮询准备就绪的key
6. Selector监听到新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路
7. 设置客户端链路为非阻塞模式
8. 将新接入的客户端连接注册到Reactor线程的Selector上,监听读操作,读取客户端发送的网络消息
9. 异步读取客户端消息到缓冲区
10.对Buffer编解码,处理半包消息,将解码成功的消息封装成Task
11.将应答消息编码为Buffer,调用SocketChannel的write将消息异步发送给客户端

NIOServer.java:

public class NIOServer {


    private static Selector selector;

    public static void main(String[] args) {
        init();
        listen();
    }

    private static void init() {
        ServerSocketChannel serverSocketChannel = null;

        try {
            selector = Selector.open();

            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(9000));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NioServer 启动完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void listen() {
        while (true) {
            try {
                selector.select();
                Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();
                while (keysIterator.hasNext()) {
                    SelectionKey key = keysIterator.next();
                    keysIterator.remove();
                    handleRequest(key);
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }

    private static void handleRequest(SelectionKey key) throws IOException {
        SocketChannel channel = null;
        try {
            if (key.isAcceptable()) {
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                channel = serverSocketChannel.accept();
                channel.configureBlocking(false);
                System.out.println("接受新的 Channel");
                channel.register(selector, SelectionKey.OP_READ);
            }

            if (key.isReadable()) {
                channel = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int count = channel.read(buffer);
                if (count > 0) {
                    System.out.println("服务端接收请求:" + new String(buffer.array(), 0, count));
                    channel.register(selector, SelectionKey.OP_WRITE);
                }
            }

            if (key.isWritable()) {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                buffer.put("收到".getBytes());
                buffer.flip();

                channel = (SocketChannel) key.channel();
                channel.write(buffer);
                channel.register(selector, SelectionKey.OP_READ);
            }
        } catch (Throwable t) {
            t.printStackTrace();
            if (channel != null) {
                channel.close();
            }
        }
    }
}

NIOClient.java:

public class NIOClient {

    public static void main(String[] args) {
        new Worker().start();
    }

    static class Worker extends Thread {
        @Override
        public void run() {
            SocketChannel channel = null;
            Selector selector = null;
            try {
                channel = SocketChannel.open();
                channel.configureBlocking(false);

                selector = Selector.open();
                channel.register(selector, SelectionKey.OP_CONNECT);
                channel.connect(new InetSocketAddress(9000));
                while (true) {
                    selector.select();
                    Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();
                    while (keysIterator.hasNext()) {
                        SelectionKey key = keysIterator.next();
                        keysIterator.remove();

                        if (key.isConnectable()) {
                            System.out.println();
                            channel = (SocketChannel) key.channel();

                            if (channel.isConnectionPending()) {
                                channel.finishConnect();

                                ByteBuffer buffer = ByteBuffer.allocate(1024);
                                buffer.put("你好".getBytes());
                                buffer.flip();
                                channel.write(buffer);
                            }

                            channel.register(selector, SelectionKey.OP_READ);
                        }

                        if (key.isReadable()) {
                            channel = (SocketChannel) key.channel();
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            int len = channel.read(buffer);

                            if (len > 0) {
                                System.out.println("[" + Thread.currentThread().getName()
                                        + "]收到响应:" + new String(buffer.array(), 0, len));
                                Thread.sleep(5000);
                                channel.register(selector, SelectionKey.OP_WRITE);
                            }
                        }

                        if(key.isWritable()) {
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            buffer.put("你好".getBytes());
                            buffer.flip();

                            channel = (SocketChannel) key.channel();
                            channel.write(buffer);
                            channel.register(selector, SelectionKey.OP_READ);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally{
                if(channel != null){
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if(selector != null){
                    try {
                        selector.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

распечатать результат:

// Server端
NioServer 启动完成
接受新的 Channel
服务端接收请求:你好
服务端接收请求:你好
服务端接收请求:你好

// Client端
[Thread-0]收到响应:收到
[Thread-0]收到响应:收到
[Thread-0]收到响应:收到

4. Резюме

Просмотрите использованиеNIOЭтапы разработки серверной программы:

  1. СоздайтеServerSocketChannelи пул потоков бизнес-процессов.
  2. Привяжите порт прослушивания и настройте его в неблокирующем режиме.
  3. СоздайтеSelector, ранее созданныйServerSocketChannelзарегистрироваться наSelectorвкл., мониторSelectionKey.OP_ACCEPT.
  4. Выполнение циклаSelector.select()`` 方法,轮询就绪的 Канал`.
  5. опрос готовChannel, если вOP_ACCEPTСтатус, указывающий, что это новый клиентский доступ, вызывающийServerSocketChannel.acceptПолучите новых клиентов.
  6. Настроить новый доступSocketChannelдля неблокирующего режима и зарегистрируйтесь вSelectorвкл., мониторOP_READ.
  7. Если опросChannelстатусOP_READ, указывающий, что есть новые готовые пакеты для чтения, а затем построитьByteBufferобъект, чтение данных.

Из этих шагов мы в основном знаем, что знания, с которыми должны быть знакомы разработчики:

  1. jdk-nioПредусмотрено несколько ключевых классов:Selector , SocketChannel , ServerSocketChannel , FileChannel ,ByteBuffer ,SelectionKey
  2. Необходимо знать сетевые знания: распаковка липких пакетов tcp, сетевая флэш-память, переполнение тела пакета и повторная передача и т. д.
  3. надо знатьlinuxБазовая реализация, как ее правильно закрытьchannel, как выйти из системыselector, как избежатьselectorслишком часто
  4. нужно знать, какclientконечная прибыльserverВозвращаемое значение терминала, а затем возвращаемое на передний план, как ждать или как сделать механизм предохранителя
  5. Необходимо знать сериализацию объектов и алгоритм сериализации
  6. Пропустите и подождите, потому что мне уже немного неудобно. Как программист, я привык к удобным и простым API, и я могу написать более надежный и безошибочный код, не зная слишком много о базовых деталях...

Недостатки нативного API NIO:

① Компоненты NIO сложны:использовать роднойNIOДля разработки серверной части и клиентской стороны необходимо задействовать канал сокета сервера (ServerSocketChannel), канал сокета (SocketChannel), Селектор (Selector), буфер (ByteBuffer) и другие компоненты, принципы и API этих компонентов должны быть знакомы сNIOразработка и отладка, а затем необходимо отлаживать и оптимизировать приложение

② Фонд развития NIO: NIOПорог несколько выше, и для разработки и оптимизации разработчикам необходимо освоить многопоточность, сетевое программирование и т. д.NIOприложение для сетевого общения

③ Базовая обработка передачи собственного модуля связи сети разработки API:Передача по сети не только реализует функцию передачи данных сервера и клиента, но также обрабатывает различные нештатные ситуации, такие как механизм отключения и повторного подключения, обработка перегрузки сети, обработка исключений, обработка залипших пакетов, обработка распаковки, механизм кэширования и т. д. Проблема в том, что это функция, которую должны иметь все зрелые сетевые приложения, иначе можно сказать, что это только демо-версия начального уровня.

④ ОШИБКА NIO: NIOЕсть некоторые ошибки в себе, такие какEpoll, в результате чего селектор (Selector) пустой опрос, не разрешенный в JDK 1.7

NettyсуществуетNIOНа основе , он инкапсулирует нативную JavaNIO API, какие из вышеперечисленных проблем были решены?

Сравнил Java Nio, используйтеNettyПрограммы развития, упрощение каких шагов это? ... Эта серия вопросов, и поэтому мы также собираемся задавать вопросы. Но только потому, что эта презентацияNIOСоответствующие знания, без введенияNetty APIиспользовать, поэтому представитьNetty APIПреимущества использования простой разработки и низкого порога немного несостоятельны. Тогда оставь это позади и открой для всехNettyУчебное путешествие, чтобы узнать, что все считают хорошимNettyТак ли хороши слухи о реках и озерах?

С нетерпением ждем последующегоNettyОтправиться в путешествие!