Обмен технологиями

Полная грамотность в сетевой модели Java

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Обзор

Описывает базовые знания NIO на уровне ava для базового обзора.

1. Обзор НИО

В Java NIO (неблокирующий ввод-вывод или новый ввод-вывод) — это новый набор API операций ввода-вывода, представленный в Java SE 1.4 и последующих версиях.

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

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

2. Три основных компонента NIO в Java

1. Каналы

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

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

2. Буферы

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

  • Емкость: максимальное количество элементов, которые может хранить буфер.
  • Позиция: индекс элемента, над которым в данный момент ведется работа, который меняется при чтении или записи данных.
  • Лимит: В режиме чтения максимальное значение, которого может достичь позиция. В режиме записи позиция не может превышать это значение.

Основные типы буферов:ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferиDoubleBuffer, каждый тип соответствует примитивному типу данных.

3. Селекторы

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

К основным методам селекторов относятся:

  • select(): Блокируется до тех пор, пока хотя бы один канал не будет готов к операциям ввода-вывода.
  • selectedKeys(): Возвращает набор, содержащий объекты SelectionKey всех подготовленных каналов.
  • wakeup(): Прерывание заблокированоselect()передача.

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

3. Сетевое программирование

1. Блокировка ввода-вывода

Это наиболее традиционная модель ввода-вывода. В Java традиционные API-интерфейсы Socket и ServerSocket основаны на блокировке ввода-вывода. В этом режиме, когда поток инициирует операцию чтения или записи, он блокируется до завершения операции ввода-вывода. Если для операции чтения нет данных для чтения или операция записи не может быть завершена немедленно, поток будет ждать завершения операции.

Функции:

  • Простая модель программирования: Легко понять и реализовать.
  • Занятие ресурсов: для каждого соединения требуется независимый поток, что приводит к ограниченному количеству потоков и высокому потреблению ресурсов.
  • Ограничения производительности: в сценариях с высоким уровнем параллелизма переключение потоков и контекста обходится дорого и может легко стать узким местом.

2. Неблокирующий ввод-вывод

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

Функции:

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

3. Мультиплексирование

Мультиплексирование заключается в одновременном контроле нескольких файловых дескрипторов через один поток и работе с дескриптором только тогда, когда он готов (обычно это означает, что данные доступны для чтения или буфер записи доступен для записи). Селекторы используются в Java для реализации мультиплексирования.

Функции:

  • Высокий параллелизм: один поток может обрабатывать тысячи соединений, что значительно повышает пропускную способность сервера.
  • Эффективный: Только когда операция ввода-вывода готова, поток будет пробужден, чтобы избежать ненужного переключения потока.
  • Экономия ресурсов: по сравнению с блокировкой ввода-вывода серверы с моделью мультиплексирования могут более эффективно использовать системные ресурсы.

Уведомление:

В практических приложениях неблокирующий ввод-вывод часто используется в сочетании с мультиплексированием. Например, сервер может использовать селектор для мониторинга нескольких SocketChannel. Если в SocketChannel есть данные, которые можно читать или записывать, селектор уведомит сервер, и сервер будет обрабатывать этот конкретный SocketChannel неблокирующим способом.

4.NIO против BIO

4.1 поток против канала

В Java Stream и Channel — это два разных способа обработки потоков данных. Они принадлежат стандартной библиотеке ввода-вывода Java и библиотеке NIO соответственно. Вот подробное сравнение двух концепций:

Транслировать

Stream является частью стандартной модели ввода-вывода Java (блокирующий ввод-вывод), которая обеспечивает способ последовательного чтения и записи данных. Поток разделен на два типа: InputStream и OutputStream, которые используются для чтения и записи данных соответственно.Эти потоки могут быть потоками байтов (например,InputStream, OutputStream) или поток символов (например,Reader, Writer)。

Функции:

  • Обструктивный: По умолчанию потоковые операции блокируются, то есть, если нет данных для чтения для операции чтения или операция записи не может быть завершена немедленно, вызывающий поток будет заблокирован до завершения операции.
  • Однонаправленность: каждый поток имеет направление: только чтение или только запись.
  • автоматическое отключение: после использования Stream вам обычно нужно вызвать егоclose() метод освобождения ресурсов.Начиная с Java 7, оператор try-with-resources может автоматически закрывать реализацию.AutoCloseableРесурсы интерфейса, включая Stream.

Канал

Channel является частью модели Java NIO (New IO), которая обеспечивает более высокий уровень абстракции, чем Stream, что позволяет более эффективно обрабатывать данные. Каналы могут быть двунаправленными, то есть их можно использовать для чтения и записи данных.Основные типы каналов включают в себяFileChannelSocketChannelServerSocketChannelиDatagramChannel

Функции:

  • неблокирующий :Канал можно настроить в блокирующем или неблокирующем режиме. В неблокирующем режиме, если в операции чтения нет данных для чтения или операция записи не может быть завершена немедленно, операция немедленно возвращается вместо блокировки потока.
  • Буферные операции:Операции с каналом всегда выполняются через буфер (Buffer). Данные считываются из канала в буфер или записываются из буфера в канал.
  • Мультиплексирование: Channel может использоваться вместе с Selector для достижения мультиплексирования, позволяя одному потоку контролировать операции ввода-вывода нескольких каналов и улучшать возможности одновременной обработки.

Подведем итог

  • Применимая сцена: Stream больше подходит для обработки небольших наборов данных или простых файловых операций, тогда как Channel больше подходит для обработки больших объемов данных, особенно сетевых коммуникаций и больших файловых операций, поскольку обеспечивает более высокую производительность и лучшие возможности параллелизма.
  • Сложность программирования: API Stream относительно прост и удобен в использовании, тогда как API Channel и NIO более сложны, но предоставляют более мощные функции и более высокую эффективность;
  • Управление ресурсами: будь то поток или канал, ресурсами следует управлять соответствующим образом и обеспечивать их закрытие, когда они больше не нужны, чтобы избежать утечек ресурсов.

4.2. Модель ИО.

В Java модели операций ввода-вывода можно классифицировать по двум измерениям: синхронный/асинхронный и блокирующий/неблокирующий.

4.2.1 Синхронная блокировка ввода-вывода

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

Функции

  • Простой и удобный в использовании, логика кода понятна.
  • Для каждой операции ввода-вывода требуется монопольный поток, который не подходит для сценариев с высоким уровнем параллелизма и может привести к исчерпанию ресурсов потока.
  • Обычно используется вInputStreamOutputStreamSocketиServerSocketсцена.

Пример:

Используйте традиционныеInputStreamиOutputStreamДля чтения и записи файлов:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class SyncBlockingIOExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             FileOutputStream fos = new FileOutputStream("output.txt")) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.2.2 Синхронный неблокирующий ввод-вывод

определение : при синхронном неблокирующем вводе-выводе поток не будет блокироваться после вызова операции ввода-вывода. Если операцию невозможно завершить немедленно, метод немедленно завершает работу, обычно возвращая специальное значение (например, -1 или 0) или выдавая исключение, указывающее, что операция не завершена.

Функции

  • Статус операции необходимо запрашивать до завершения операции.
  • Более высокая загрузка потоков, поскольку потоки могут выполнять другие задачи во время ожидания ввода-вывода.
  • Реализация более сложна и требует обработки опросов и проверок статуса.
  • На основе Java НИОChannelsиBuffers, позвонивconfigureBlocking(false)Установите канал в неблокирующий режим.

Пример:

Использование НИОFileChannelДля неблокирующего чтения и записи:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;

public class SyncNonBlockingIOExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             FileOutputStream fos = new FileOutputStream("output.txt");
             FileChannel inChannel = fis.getChannel();
             FileChannel outChannel = fos.getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            inChannel.configureBlocking(false);
            while (inChannel.read(buffer) > 0) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

4.2.3 Синхронное мультиплексирование ввода-вывода

определение: Использование модели синхронного мультиплексного ввода-вывода.Selector(селектор) для мониторинга несколькихChannel событие.Когда поток вызываетSelector.select(), он будет блокироваться до тех пор, пока не появится хотя бы одинChannelПроисходят события (такие как чтение, запись, запрос на соединение и т. д.).

Функции

  • Позволяет одному потоку управлять несколькимиChannel, улучшает параллелизм.
  • проходитьSelectorиSelectionKeyМеханизм, который может эффективно обрабатывать большое количество одновременных соединений.
  • Подходит для сценариев сетевых серверов, таких как веб-серверы и серверы чата.
  • На основе платформы Java NIO.

Пример:

Использование НИОFileChannelДля неблокирующего чтения и записи:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

public class SyncMultiplexingIOExample {
    public static void main(String[] args) throws IOException {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                if (selector.select() > 0) {
                    Set<SelectionKey> keys = selector.selectedKeys();
                    for (SelectionKey key : keys) {
                        if (key.isAcceptable()) {
                            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                            SocketChannel clientChannel = ssc.accept();
                            clientChannel.configureBlocking(false);
                            clientChannel.register(selector, SelectionKey.OP_READ);
                        }
                    }
                    keys.clear();
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

4.2.4 Асинхронный неблокирующий ввод-вывод

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

Функции

  • Самая эффективная модель ввода-вывода: потоки вообще не блокируются и могут немедленно выполнять другие задачи.
  • проходитьAsynchronousChannelИнтерфейс и реализация его подкласса, напримерAsynchronousFileChannelиAsynchronousSocketChannel
  • Подходит для сценариев с высоким уровнем параллелизма и высокой производительности, таких как обработка больших данных и сетевые серверы с высокой нагрузкой.
  • В Java 7 появилась асинхронная неблокирующая модель ввода-вывода.

Пример:

Использование НИОAsynchronousFileChannelВыполните асинхронное чтение и запись файлов:

import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;

public class AsyncNonBlockingIOExample {
    public static void main(String[] args) throws IOException, InterruptedException {
        AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(Paths.get("input.txt"), StandardOpenOption.READ);
        AsynchronousFileChannel outChannel = AsynchronousFileChannel.open(Paths.get("output.txt"), StandardOpenOption.WRITE);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        CountDownLatch latch = new CountDownLatch(1);
        inChannel.read(buffer, 0, buffer, 0, (channel, result) -> {
            buffer.flip();
            outChannel.write(buffer, 0, buffer, 0, null);
            latch.countDown();
        });
        latch.await();
        inChannel.close();
        outChannel.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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

  • Системы Windows реализуют настоящий асинхронный ввод-вывод через IOCP.
  • Асинхронный ввод-вывод в системе Linux был представлен в версии 2.6, но его базовая реализация по-прежнему использует мультиплексирование для имитации асинхронного ввода-вывода, и преимущества в производительности нет.

4.2.5 Асинхронный блокирующий ввод-вывод

определение : Теоретически такой модели не существует, поскольку «асинхронность» означает, что операция выполняется в фоновом режиме и поток не блокируется. Поэтому асинхронная блокировка ввода-вывода является противоречивой концепцией и не появится на практике.

4.2.6 Резюме

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

4.3 Нулевая копия

Концепция нулевого копирования

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

Источник нулевой копии

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

Ключевые технические моменты

  • Прямой ввод-вывод: позволяет приложениям напрямую обращаться к дисковым устройствам, минуя механизм кэширования файловой системы.
  • Отображение памяти (MMAP): Сопоставить файлы с памятью, чтобы к данным файла можно было обращаться как с памятью, что сокращает копирование.
  • Отправить файл: системный вызов Linux, который может передавать данные непосредственно из одного файлового дескриптора в другой, избегая промежуточных копий.
  • DMA (прямой доступ к памяти): Технология аппаратного уровня, позволяющая передавать данные напрямую между устройством и памятью без участия процессора.

Реализация на Java:

Java поддерживает технологию нулевого копирования через структуру NIO (New I/O). НИО представилFileChannelиSocketChannel и другие классы, которые обеспечивают более эффективные операции ввода-вывода. Конкретно,FileChannel.transferTo()иFileChannel.transferFrom()Методы могут передавать данные непосредственно из файлового канала в канал сокета или наоборот, не загружая данные в буфер, что обеспечивает нулевое копирование.

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

Пример использования ByteBuffer:

ByteBufferи другиеBufferкласс (например,CharBufferShortBufferи т. д.) предоставляют буферы, которые можно заполнять или очищать без прямого копирования между пространством пользователя и пространством ядра.ByteBufferМожет использоваться прямо или косвенно в технологии нулевого копирования:

  1. Прямой буферByteBuffer.allocateDirect(size)СозданныйByteBuffer Экземпляры отображаются непосредственно в физическую память, минуя кучу Java.Когда такой буфер сравнивается сFileChannelилиSocketChannel При совместном использовании данные могут передаваться напрямую между физической памятью и аппаратными устройствами без необходимости создания дополнительных копий через кучу Java. Это обеспечивает полное отсутствие копирования на некоторых платформах.
  2. использоватьFileChannelизtransferTo()иtransferFrom(): Эти методы позволяют хранить данные непосредственно вFileChannelиSocketChannelпередается междуByteBuffer в качестве посредника. Это означает, что данные можно передавать напрямую с диска в сеть или наоборот без необходимости копирования между пространством пользователя и пространством ядра.
  3. использоватьByteBufferизwrap()методwrap()позволяет обернуть существующий массив байтов или другой буфер вByteBuffer , поэтому нет необходимости копировать данные в новый буфер. Это полезно, чтобы избежать ненужного копирования данных.
  4. использоватьByteBufferизslice()методslice() метод создает представление буфера без копирования базовых данных.Это означает, что большоеByteBufferРазделить на несколько буферов меньшего размера без копирования данных.

Объедините ByteBuffer с FileChannel:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class ZeroCopyExample {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");
        try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            long transferred = fileChannel.transferTo(0, fileChannel.size(), System.out);
            System.out.println("Transferred bytes: " + transferred);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Уведомление:

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