技術共有

Java ネットワーク モデルに関する完全なリテラシー

2024-07-12

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

概要

基本的な復習のために、NIO の基本的な知識を ava レベルで説明します。

1.NIOの概要

Java では、NIO (ノンブロッキング I/O または New I/O) は、Java SE 1.4 以降のバージョンで導入された新しい入出力操作 API のセットです。

従来の IO モデルと比較して、より高い効率と優れた同時処理機能を提供します。 NIO の主な機能は、ノンブロッキング機能です。これにより、単一のスレッドで複数の I/O チャネルを管理できるようになり、同時実行性の高いシナリオでのアプリケーションのパフォーマンスが大幅に向上します。

Java NIO の使用シナリオは、多数の同時接続を処理する必要がある状況に特に適しています。たとえば、ネットワーク サーバーでは、接続ごとに独立したスレッドを割り当てる必要がなく、1 つのスレッドで数千のクライアント接続を管理できます。システムリソースの消費を大幅に削減し、処理能力を向上させることができます。

2. Java における NIO の 3 つの主要コンポーネント

1. チャンネル

チャネルは Java NIO のデータ フローの媒体であり、双方向であり、データの読み取りまたは書き込みに使用できます。チャネルの主な利点は、チャネルが非ブロックであることです。つまり、チャネルでイベントが発生しない場合、スレッドはブロックされず、代わりに他のタスクを処理できます。主なチャネルの種類は次のとおりです。

  • FileChannel: ファイルの読み取りおよび書き込み操作に使用され、バッファーからファイルへのデータの書き込み、またはファイルからバッファーへのデータの読み取りに使用できます。
  • SocketChannel:ネットワーク通信におけるTCP接続に使用され、データの読み書きに使用できます。
  • ServerSocketChannel: 従来の ServerSocket と同様に、新しい SocketChannel 接続を受け入れるために使用されます。
  • DatagramChannel: データグラムを送受信できる UDP 通信に使用されます。

2. バッファー

バッファは、NIO にデータを保存するために使用されるオブジェクトであり、データの書き込みと読み取りを行うことができます。バッファには特定の容量があり、位置と制限という 2 つの重要なプロパティがあります。

  • 容量: バッファーに格納できる要素の最大数。
  • 位置: 現在操作されている要素のインデックス。データの読み取りまたは書き込み時に変更されます。
  • 限界: 読み取りモードでは、位置が到達できる最大値。書き込みモードでは、位置はその値を超えることはできません。

バッファの主な種類は次のとおりです。ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferそしてDoubleBuffer、各型はプリミティブ データ型に対応します。

3. セレクター

セレクターは、NIO のマルチプレクサーで、単一のスレッドが読み取り、書き込み、接続、イベントの受信など、複数のチャネルからのイベントを監視できるようにします。セレクターは、チャネルの 1 つが I/O 操作の準備ができたときにアプリケーションに通知します。セレクターを使用すると、接続ごとにスレッドを作成する必要がないため、ネットワーク アプリケーションの同時処理能力が大幅に向上します。

セレクターの主なメソッドは次のとおりです。

  • select(): 少なくとも 1 つのチャネルが I/O 操作の準備ができるまでブロックします。
  • selectedKeys(): 準備されたすべてのチャネルの SelectionKey オブジェクトを含む Set を返します。
  • wakeup(): 割り込みがブロックされていますselect()移行。

SelectionKeyこれは、セレクターとチャネルの間の関連付けであり、チャネル、セレクター、対象イベント コレクション、準備完了イベント コレクションなど、セレクター上のチャネルの登録ステータスを表します。

3. ネットワークプログラミング

1. I/O のブロック

これは最も伝統的な I/O モデルであり、Java では、従来の Socket API と ServerSocket API はブロッキング I/O に基づいています。このモードでは、スレッドが読み取りまたは書き込み操作を開始すると、I/O 操作が完了するまでスレッドはブロックされます。読み取り操作で読み取るデータがない場合、または書き込み操作をすぐに完了できない場合、スレッドは操作が完了するまで待機します。

特徴:

  • シンプルなプログラミングモデル: 理解と実装が簡単です。
  • 資源占有: 各接続には独立したスレッドが必要なので、スレッド数が制限され、リソースの消費量が多くなります。
  • パフォーマンスの制限: 同時実行性の高いシナリオでは、スレッド切り替えとコンテキスト切り替えにコストがかかり、簡単にボトルネックになる可能性があります。

2. ノンブロッキング I/O

ノンブロッキング I/O は Java NIO (New I/O) フレームワークの一部であり、スレッドがブロックされることなく読み取りおよび書き込み操作を開始できるようにします。読み取り操作で読み取るデータがない場合、または書き込み操作をすぐに完了できない場合、スレッドは中断されず、他のタスクの実行を続行できます。

特徴:

  • 柔軟性: スレッドは、I/O 操作の待機中にブロックされないため、より多くの接続を処理できます。
  • 複雑さの増加: プログラマは操作が完了したかどうかを明示的に確認する必要があるため、プログラミングの難易度が高くなります。
  • パフォーマンスの向上: 同時実行性の高いシナリオでは、スレッドの切り替えによって発生するオーバーヘッドを大幅に削減できます。

3. 多重化

多重化とは、1 つのスレッドを通じて複数のファイル記述子を同時に監視し、準備ができたときにのみ記述子を操作することです (通常、データが読み取り可能であるか、書き込みバッファーが書き込み可能であることを意味します)。 Java ではセレクターを使用して多重化を実装します。

特徴:

  • 高い同時実行性: 1 つのスレッドで数千の接続を処理できるため、サーバーのスループットが大幅に向上します。
  • 効率的: I/O 操作の準備ができた場合にのみ、不必要なスレッドの切り替えを避けるためにスレッドが起動されます。
  • 省資源: ブロッキング I/O と比較して、多重化モデルのサーバーはシステム リソースをより効率的に利用できます。

知らせ:

実際のアプリケーションでは、ノンブロッキング I/O が多重化と組み合わせて使用​​されることがよくあります。たとえば、サーバーはセレクターを使用して複数の SocketChannel を監視できます。SocketChannel に読み取りまたは書き込み可能なデータがある場合、セレクターはサーバーに通知し、サーバーはこの特定の SocketChannel を非ブロック方式で処理します。

4.NIO 対 BIO

4.1 ストリームとチャネル

Java では、ストリームとチャネルはデータ ストリームを処理する 2 つの異なる方法で、それぞれ Java の標準 IO ライブラリと NIO ライブラリに属します。 2 つの概念の詳細な比較は次のとおりです。

ストリーム

ストリームは Java の標準 IO (ブロッキング IO) モデルの一部であり、データを順次読み書きする方法を提供します。 ストリームは、InputStream と OutputStream の 2 種類に分けられ、それぞれデータの読み取りと書き込みに使用されます。これらのストリームはバイト ストリーム (たとえば、InputStream, OutputStream) または文字ストリーム (例:Reader, Writer)。

特徴:

  • 邪魔な注: デフォルトでは、Stream 操作はブロックされます。つまり、読み取り操作で読み取るデータがない場合、または書き込み操作をすぐに完了できない場合、呼び出しスレッドは操作が完了するまでブロックされます。
  • 一方向性: 各ストリームには、読み取り専用または書き込み専用の方向があります。
  • 自動シャットダウン: Stream を使用した後、通常はそれを呼び出す必要がありますclose()リソースを解放するメソッド。Java 7 以降、try-with-resources ステートメントは実装を自動的に閉じることができます。AutoCloseableストリームを含むインターフェイス リソース。

チャネル

チャネルは Java NIO (New IO) モデルの一部であり、ストリームよりも高いレベルの抽象化を提供し、より効率的なデータ処理を可能にします。 チャネルは双方向にすることができます。つまり、データの読み取りと書き込みに使用できます。主なチャネル タイプには次のものがあります。FileChannelSocketChannelServerSocketChannelそしてDatagramChannel

特徴:

  • ノンブロッキング :チャンネルはブロッキング モードまたはノンブロッキング モードで設定できます。非ブロッキング モードでは、読み取り操作に読み取るデータがない場合、または書き込み操作をすぐに完了できない場合、操作はスレッドをブロックせずにすぐに戻ります。
  • バッファ操作: チャネル操作は常にバッファ (Buffer) を介して実行され、データはチャネルからバッファに読み取られるか、バッファからチャネルに書き込まれます。
  • 多重化: Channel を Selector と併用して多重化を実現すると、1 つのスレッドで複数の Channel の I/O 操作を監視し、同時処理能力を向上させることができます。

要約する

  • 該当シーン: ストリームは、より小さなデータ セットや単純なファイル操作の処理に適していますが、チャネルは、より高いパフォーマンスと優れた同時実行機能を提供するため、大量のデータの処理、特にネットワーク通信や大きなファイル操作に適しています。
  • プログラミングの複雑さ: Stream の API は比較的シンプルで使いやすいですが、Channel と NIO の API はより複雑ですが、より強力な機能と高い効率を提供します。
  • 資源管理: ストリームであってもチャネルであっても、リソースは適切に管理され、リソース リークを避けるために必要がなくなった場合には確実に閉じられるようにする必要があります。

4.2. IOモデル

Java では、I/O 操作モデルは、同期/非同期、およびブロッキング/ノンブロッキングの 2 つの次元に基づいて分類できます。

4.2.1 同期ブロッキング I/O

意味 : 同期ブロッキング I/O は、スレッドが I/O 操作 (読み取りや書き込みなど) を呼び出すと、その操作が完了するまでブロックされます。これは、操作が完了するまでスレッドは他のタスクを実行できないことを意味します。

特徴

  • シンプルで使いやすく、コードロジックは明確です。
  • 各 I/O 操作には排他スレッドが必要ですが、これは同時実行性の高いシナリオには適しておらず、スレッド リソースの枯渇につながる可能性があります。
  • 一般的に使用されるのは、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 同期ノンブロッキング I/O

意味 : 同期ノンブロッキング I/O では、I/O 操作の呼び出し後にスレッドはブロックされません。操作をすぐに完了できない場合、メソッドはすぐに戻り、通常は特別な値 (-1 や 0 など) を返すか、操作が完了していないことを示す例外をスローします。

特徴

  • 操作が完了するまで、操作ステータスをポーリングする必要があります。
  • スレッドは I/O を待機している間に他のタスクを実行できるため、スレッド使用率が高くなります。
  • 実装はより複雑で、ポーリングとステータス チェックを処理する必要があります。
  • Java NIO に基づくChannelsそしてBuffers、電話することでconfigureBlocking(false)チャネルをノンブロッキング モードに設定します。

例:

NIO の使用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 同期多重化 I/O

意味: 同期多重I/Oモデルの使用方法Selector(セレクター) 複数を監視する場合Channelイベント。スレッドが呼び出すときSelector.select()、少なくとも 1 つ存在するまでブロックされます。Channelイベント(読み取り可能、書き込み可能、​​接続要求など)が発生します。

特徴

  • 1 つのスレッドで複数のスレッドを管理できるようにするChannel、同時実行性が向上します。
  • 合格SelectorそしてSelectionKey大量の同時接続を効率的に処理できる仕組み。
  • Web サーバーやチャット サーバーなどのネットワーク サーバー シナリオに適しています。
  • Java NIO フレームワークに基づいています。

例:

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 非同期ノンブロッキング I/O

意味 : 非同期ノンブロッキング I/O モデルでは、スレッドは I/O 操作の開始後、操作の完了を待たずにすぐに戻ります。操作が完了すると、コールバック関数またはイベント通知を通じてスレッドに非同期に通知されます。

特徴

  • 最も効率的な I/O モデルでは、スレッドはまったくブロックされず、他のタスクをすぐに実行できます。
  • 合格AsynchronousChannelインターフェイスとそのサブクラスの実装 (例:AsynchronousFileChannelそしてAsynchronousSocketChannel
  • ビッグ データ処理や高負荷のネットワーク サーバーなど、同時実行性とパフォーマンスが高いシナリオに適しています。
  • Java 7 では、非同期ノンブロッキング I/O モデルが導入されました。

例:

NIO の使用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 を通じて真の非同期 IO を実装します。
  • Linux システムの非同期 IO はバージョン 2.6 で導入されましたが、その基礎となる実装では依然として多重化を使用して非同期 IO をシミュレートしており、パフォーマンス上の利点はありません。

4.2.5 非同期ブロッキング I/O

意味 : 「非同期」とは操作がバックグラウンドで実行され、スレッドがブロックされないことを意味するため、理論的にはこのモデルは存在しません。したがって、非同期ブロッキング I/O は矛盾した概念であり、実際には登場しません。

4.2.6 概要

適切な I/O モデルを選択することは、パフォーマンスとリソース管理にとって重要です。同時実行性の高いシナリオでは、同期ノンブロッキング I/O モデルまたは同期多重化 I/O モデルが一般的に選択されます。極端なパフォーマンスと応答速度が必要なシナリオでは、非同期ノンブロッキング I/O モデルが最初の選択肢となります。

4.3 ゼロコピー

ゼロコピーのコンセプト

ゼロコピー テクノロジとは、ある場所から別の場所へのデータ送信プロセス中に、ユーザー空間とカーネル空間の間でデータをコピーする必要がないか、少なくともコピーの数を減らすことで、データの効率が向上することを意味します。システム。従来の I/O 操作では、データがネットワークまたはディスクから読み取られると、まずカーネル空間のバッファにコピーされ、次にカーネル空間からユーザー空間のバッファにコピーされます。これに対し、ゼロコピーでは、データをカーネル空間で直接処理したり、カーネル空間からネットワークデバイスに直接転送したりできるため、CPU のコピー動作が減り、システムのオーバーヘッドが削減され、データ転送の効率が向上します。

ゼロコピーのソース

ゼロ コピーの概念は、従来の I/O 操作におけるデータ コピーによって引き起こされるパフォーマンスのボトルネックを解決することを目的として、オペレーティング システムの設計で初めて登場しました。初期のコンピュータ システムでは、すべての I/O 操作にユーザー空間とカーネル空間の間で複数のデータ コピーが必要でしたが、高速ネットワークと大容量ディスクが普及した後、これが徐々にパフォーマンスのボトルネックになりました。

技術的なポイント

  • ダイレクトI/O: ファイル システムのキャッシュ メカニズムをスキップして、アプリケーションがディスク デバイスに直接アクセスできるようにします。
  • メモリマッピング (MMAP): ファイルをメモリにマップして、ファイル データにメモリのようにアクセスできるようにし、コピーを減らします。
  • ファイルを送信: 中間コピーを回避して、あるファイル記述子から別のファイル記述子にデータを直接転送できる Linux システム コール。
  • DMA(ダイレクトメモリアクセス): CPU を介さずにデバイスとメモリ間でデータを直接転送できるハードウェア レベルのテクノロジ。

Java での実装:

Java は、NIO (New I/O) フレームワークを通じてゼロコピー テクノロジをサポートします。 NIO導入FileChannelそしてSocketChannelおよび他のクラスは、より効率的な I/O 操作を提供します。具体的には、FileChannel.transferTo()そしてFileChannel.transferFrom()メソッドは、データをバッファにロードせずに、ファイル チャネルからソケット チャネルに、またはその逆にデータを直接転送できるため、ゼロ コピーを実現できます。

たとえば、大きなファイルの内容をネットワークに送信する必要があるとします。従来のアプローチでは、まずファイルの内容をバッファに読み取り、次にバッファの内容をネットワークに書き込みます。これには 2 つのコピー操作が含まれます。1 つはディスクからバッファーへ、もう 1 つはバッファーからネットワークへです。使用中transferTo()この方法を使用すると、中間バッファを必要とせずにデータをディスクからネットワークに直接転送できるため、コピー数が削減され、コピーゼロが実現します。

ByteBuffer を使用した例:

ByteBufferその他Bufferクラス(など)CharBufferShortBufferなど) ユーザー空間とカーネル空間の間のコピーを直接関与させることなく、埋めたり空にしたりできるバッファーを提供します。ByteBufferゼロコピーテクノロジーで直接的または間接的に使用できます。

  1. ダイレクトバッファByteBuffer.allocateDirect(size)作成したByteBufferインスタンスは Java ヒープをバイパスして、物理メモリに直接マッピングされます。このようなバッファを比較すると、FileChannelまたはSocketChannel一緒に使用すると、Java ヒープを介して追加のコピーを必要とせずに、物理メモリとハードウェア デバイスの間でデータを直接転送できます。これにより、一部のプラットフォームで真のゼロコピーが可能になります。
  2. 使用FileChanneltransferTo()そしてtransferFrom(): これらの方法では、データを直接保存できます。FileChannelそしてSocketChannel間で送信されるByteBuffer仲介者として。これは、ユーザー空間とカーネル空間の間でデータをコピーすることなく、データをディスクからネットワークに、またはその逆に直接転送できることを意味します。
  3. 使用ByteBufferwrap()方法wrap()メソッドを使用すると、既存のバイト配列または他のバッファをラップすることができます。ByteBufferしたがって、データを新しいバッファにコピーする必要はありません。これは、データの不必要なコピーを避けるのに役立ちます。
  4. 使用ByteBufferslice()方法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

知らせ:

実際には、完全なゼロ コピーは存在しないことに注意してください。ここで説明するゼロ コピーは、ユーザー レベルのコピーを持たないアプリケーション自体のみを対象としています。しかし、ユーザーレベルでも、コピー操作をまったく行わないことは不可能ですが、コピーを可能な限り減らすことは不可能です。したがって、ゼロコピーという用語は、実際にはデータのコピー数を減らす技術を指すことがわかります。実際のコピー操作が存在しないという意味ではありません。