ホームページ  >  記事  >  Java  >  Java NIO サンプルの使用法と特性を分析する

Java NIO サンプルの使用法と特性を分析する

王林
王林転載
2023-05-06 22:25:06808ブラウズ

Java NIO サンプルの使用法と特性を分析する


#1. Java マインド マップ

Java NIO サンプルの使用法と特性を分析する

2. I/O モデル

I/O モデルの本質は、データの送受信にどのようなチャネルが使用されるかであり、これがプログラム通信のパフォーマンスを大きく決定します。

Java は 3 つのネットワーク プログラミング モデルをサポートします: BIO、NIO、AIO

  • BIO: 同期とブロッキング、サービス実装モードは 1 つの接続と 1 つのスレッドです。つまり、クライアントは1 つの接続 リクエストを行うとき、サーバーは処理のためにスレッドを開始する必要があります。

  • NIO: 同期かつノンブロッキングのサーバー実装モードは、複数のリクエスト接続を処理するスレッドです。つまり、クライアントから送信されたリクエストはマルチプレクサに登録され、多重化されます。サーバーは、I/O リクエストの接続をポーリングし、それらを処理します。

  • AIO: 非同期ノンブロッキング、AIO は非同期チャネルの概念を導入し、プロアクター モードを採用し、プログラムの作成を簡素化し、有効なリクエストの後にのみスレッドを開始します。その特徴は、オペレーティング システムがまず完了後にサーバーに通知します。

3. BIO、NIO、AIO アプリケーション シナリオ

  • BIO 方式は、接続数が比較的少なく固定されたアーキテクチャに適しています。この方法は、サーバーのリソース要件が比較的高く、同時実行がアプリケーションに制限されている場合に適しており、JDK1.4 以前はこれしか選択肢がありませんでしたが、プログラムがシンプルで理解しやすいです。

  • NIO メソッドは、チャット サーバー、弾幕システム、サーバー間通信など、多数の接続と比較的短い接続 (軽い操作) を行うアーキテクチャに適しています。プログラミングはより複雑になり、JDK1.4 でサポートされ始めます。

  • AIO メソッドは、フォト アルバム サーバーなど、多数の接続と比較的長い接続 (重い操作) を伴うアーキテクチャで使用され、同時操作に参加するために OS を完全に呼び出します。プログラミングは比較的複雑で、JDK7 でサポートされ始めています

4. BIO プログラミングの簡単なプロセス

  • サーバーは ServerSocket を開始します。

  • クライアントはソケットを開始します。サーバーと通信するには、デフォルトでサーバーは各クライアントが通信するためのスレッドを確立する必要があります。

  • クライアントはリクエストを送信した後、まずスレッド応答があるかどうかをサーバーに問い合わせます、ない場合は待機するか拒否されます;

  • 応答がある場合、クライアントはスレッドは実行を続行する前にリクエストが終了するのを待ちます;

5 、NIO コア

NIO には 3 つのコア部分があります: セレクター (セレクター)、チャンネル (チャネル)、そしてバッファ(バッファ)。

NIO はバッファ指向、つまりブロック指向のプログラミングです。データはバッファに読み込まれ、後で処理されます。必要に応じてバッファ内でデータを前後に移動できるため、処理プロセスの柔軟性が向上します。 Use It は、ノンブロッキングで拡張性の高いネットワークを提供します。
HTTP2.0 は多重化テクノロジーを使用して、同じ接続で複数のリクエストを同時に処理できるようにしており、同時リクエストの数は HTTP1.1 よりも数桁多くなります。
つまり、NIO は 1 つのスレッドで複数のリクエストを処理できます。

6. BIO と NIO の比較

  • BIO はストリーム内のデータを処理しますが、NIO はブロック内のデータを処理します。ブロック I/O はストリーム I より効率的です。 /O. /O ははるかに高い;

  • BIO はブロッキング、NIO はノンブロッキング;

  • BIO はバイト ストリームに基づいて動作し、文字ストリーム、NIO はチャネルとバッファに基づいて動作します。データは常にチャネルからバッファに読み取られるか、バッファからチャネルに書き込まれます。セレクターは、複数のチャネル イベント (接続要求、データ到着など) を監視するために使用されるため、単一のスレッドで複数のクライアント チャネルを監視できます。

#7. NIO の 3 つの中心原則の概略図

Java NIO サンプルの使用法と特性を分析する フローチャートの説明:

  • セレクター対応 1 つのスレッド、1 つのスレッドが複数のチャネル (接続) に対応します;

  • この図は、セレクター //プログラムに登録されているチャネルが 3 つあることを示しています;

  • 各チャネルはバッファに対応します;

  • プログラムがどのチャネルに切り替わるかはイベントによって決定され、イベントは重要な概念です;

  • セレクターはさまざまなイベントに応じて各チャンネルをオンにします;

  • バッファはメモリ ブロックであり、下部には配列があります;

  • データはバッファを介して読み書きされます。これは BIO と同じです。BIO は入力ストリームまたは出力ストリームのいずれかであり、双方向にすることはできません。ただし、NIO のバッファは読み書きでき、反転することもできます。切り替えるにはメソッドが必要です。

  • チャネルは双方向であり、基礎となるオペレーティング システムのステータスを返すことができます。たとえば、Linux の場合、基礎となるオペレーティング システム チャネルは双方向です。

8. バッファ

バッファは本質的にデータの読み書きができるメモリ ブロックであり、コンテナ オブジェクト (配列を含む) として理解できます。メモリ ブロックの操作を容易にする一連のメソッドと、バッファ オブジェクトにはバッファの状態変化を追跡および記録するためのメカニズムが組み込まれています。 Channel はファイルやネットワークからデータを読み取るためのチャネルを提供しますが、読み書きされるデータは Buffer を経由する必要があります。
NIO では、Buffer は最上位の親クラスであり、抽象クラスです。

1. 一般的に使用されるバッファ サブクラスのリスト

  • ByteBuffer、バイト データをバッファに格納します;

  • ShortBuffer、文字列データをバッファに保存します;

  • CharBuffer、文字データをバッファに保存します;

  • IntBuffer、整数データを保存しますバッファに保存;

  • LongBuffer、長整数データをバッファに保存;

  • DoubleBuffer、小数をバッファに保存;

  • FloatBuffer、バッファに小数を格納します;

##2. バッファの 4 つの主要な属性

    #mark: マーク
  • position: 位置、読み書きされる次の要素のインデックス。値は、バッファ データが読み書きされるたびに変更されます。次へ 読み取りと書き込みの準備。
  • limit: バッファの現在の終了点を示します。制限を超えるバッファでは読み取りおよび書き込み操作を実行できません。また、制限は変更できます。
  • capacity: 容量、つまり、収容できるデータの最大量は、バッファの作成時に設定され、変更できません。

Java NIO サンプルの使用法と特性を分析する#3. 共通バッファ API

## JDK1.4 のときに導入されました

##public Final int Capacity( )//このバッファの容量を返します

  • public Final int Position( )//このバッファの位置を返します

  • publicfinal Bufferposition (int newPositio)//このバッファの位置を設定します

  • public Final int limit( )//このバッファ Limit を返します

  • public Final Buffer limit (int newLimit)//このバッファの制限を設定します

  • public Final Buffer mark()// マークを設定しますこのバッファの位置

  • publicfinal Buffer replace()//このバッファの位置を以前にマークされた位置にリセットします

  • publicfinal Buffer clear( )//このバッファをクリアします。つまり、各マークを初期状態に戻しますが、データは実際には消去されず、後続の操作で上書きされます

  • public Final Bufferフリップ( )//このバッファを巻き戻します

  • public Final Buffer rewind( )//このバッファを巻き戻します

  • public Final int Remaining( )/ /現在位置と制限値の間の要素の数を返します

  • public Final boolean hasRemaining( )//現在位置と制限値の間に要素があるかどうかを通知します

  • public abstract boolean isReadOnly( );//このバッファが読み取り専用バッファかどうかを通知します

  • JDK1.6で導入されたAPI

public abstract boolean hasArray();//このバッファにアクセス可能な基になる実装があるかどうかを通知します。 array

  • public abstract Object array();/ /基になる実装を返します。このバッファの配列

  • public abstract int arrayOffset();//このバッファの基礎となる実装配列内の最初のバッファ要素のオフセットを返します

  • #public abstract boolean isDirect();//このバッファがダイレクト バッファかどうかを通知します
  • 9. チャネル

1. 基本的な紹介Java NIO サンプルの使用法と特性を分析する

(1) NIO チャネルはストリームに似ています

チャネルは、同時に読み取りと書き込みが可能です。ストリームは読み取りまたは書き込みのみ可能ですが、

    チャネルはデータを非同期的に読み取りおよび書き込みできます
  • チャネルはバッファからデータを読み取りおよび書き込みできますデータを読み取るために、バッファにデータを書き込むこともできます。
  • (2) BIO のストリームは一方向です。たとえば、FileInputStream オブジェクトはデータの読み取りのみが可能ですが、ストリームNIO チャネルは双方向であり、読み取りまたは書き込みが可能です。
  • (3) チャネルは NIO のインターフェイスです

    (4) 一般的に使用されるチャネル クラスには、FileChannel、DatagramChannel、ServerSocketChannel、および SocketChannel が含まれます。 ServerSocketChanne は ServerSocket に似ており、SocketChannel は Socket に似ています。

    (5) FileChannel はファイルデータの読み書きに使用され、DatagramChannel は UDP データの読み書きに使用され、ServerSocketChannel と SocketChannel は TCP データの読み書きに使用されます。

2. FileChannel


FileChannel は主にローカル ファイルで IO 操作を実行するために使用されます。一般的なメソッドは次のとおりです:

read 、チャネルからデータを読み取り、バッファに入れます

    write、バッファ内のデータをチャネルに書き込みます
  • transferFrom、からターゲット チャネルから現在のチャネルにデータをコピーします
  • transferTo、現在のチャネルからターゲット チャネルにデータをコピーします

3. バッファとチャネルに関する注意と詳細

  • ByteBuffer は、型付きの put と get、put into データ型をサポートします。 get は、対応するデータ型を使用して取得する必要があります。そうでない場合は、BufferUnderflowException 例外が発生する可能性があります。

  • 通常のバッファを読み取り専用バッファに変換できます。

  • NIO は、ファイルをメモリ (ヒープ外のメモリ) 内で直接変更できるようにする MappedByteBuffer も提供しており、ファイルの同期方法は NIO によって完了します。

  • NIO は、複数のバッファ (バッファ配列) を介した読み取りおよび書き込み操作、つまりスキャッタリングとギャザリングもサポートします。

10. セレクター(selector)

1. 基本的な紹介

  • Java の NIO を使用します。ノンブロッキングIOモード。 1 つのスレッドを使用して複数のクライアント接続を処理でき、セレクターを使用します。

  • Selector は登録されている複数のチャネルでイベントが発生したかどうかを検出し、イベントが発生した場合はイベントを取得し、それぞれのイベントに応じて処理します。このように、複数のチャネルの管理、つまり複数の接続とリクエストの管理に 1 つのスレッドのみを使用できます。

  • 読み取りと書き込みは、接続/チャネル上に実際の読み取りおよび書き込みイベントがある場合にのみ発生するため、システムのオーバーヘッドが大幅に削減され、接続ごとにスレッドを作成する必要がなくなります。複数のスレッドを維持する必要があります。

  • 複数のスレッド間のコンテキストの切り替えによって発生するオーバーヘッドを回避します。

#2. セレクター関連のメソッド

  • ##open();//セレクター オブジェクトを取得します

  • select(long timeout);//登録されているすべてのチャネルを監視します。IO 操作が実行できる場合、対応する SelectionKey を内部コレクションに追加して返します。パラメータはタイムアウトの設定に使用されます。

  • selectedKeys();//内部コレクションからすべてのSelectionKeyを取得します。

3. 注意事項

NIO の ServerSocketChannel 関数は ServerSocket に似ており、SocketChannel 関数は Socket に似ています。

11. NIO を介した単純なサーバーとクライアントの通信

1. サーバー
package com.nezha.guor.nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.util.Iterator;public class NioServer {
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private static final int PORT = 8080;

    public NioServer() {
        try {
            //获得选择器
            selector = Selector.open();
            serverSocketChannel =  ServerSocketChannel.open();
            //绑定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            //设置非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //将该ServerSocketChannel 注册到selector
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch (IOException e) {
            System.out.println("NioServer error:"+e.getMessage());
        }
    }

    public void listen() {

        System.out.println("监听线程启动: " + Thread.currentThread().getName());
        try {
            while (true) {
                int count = selector.select();
                if(count > 0) {
                    //遍历得到selectionKey集合
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();

                        if(key.isAcceptable()) {
                            SocketChannel sc = serverSocketChannel.accept();
                            sc.configureBlocking(false);
                            sc.register(selector, SelectionKey.OP_READ);
                            System.out.println(sc.getRemoteAddress() + " 上线 ");
                        }
                        //通道发送read事件,即通道是可读的状态
                        if(key.isReadable()) {
                            getDataFromChannel(key);
                        }
                        //当前的key 删除,防止重复处理
                        iterator.remove();
                    }
                } else {
                    System.out.println("等待中");
                }
            }
        }catch (Exception e) {
            System.out.println("listen error:"+e.getMessage());
        }
    }

    private void getDataFromChannel(SelectionKey key) {
        SocketChannel channel = null;
        try {
            channel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int count = channel.read(buffer);
            //根据count的值做处理
            if(count > 0) {
                String msg = new String(buffer.array());
                System.out.println("来自客户端: " + msg);

                //向其它的客户端转发消息(排除自己)
                sendInfoToOtherClients(msg, channel);
            }
        }catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + " 离线了");
                //取消注册
                key.cancel();
            }catch (IOException ex) {
                System.out.println("getDataFromChannel error:"+ex.getMessage());
            }
        }finally {
            try {
                channel.close();
            }catch (IOException ex) {
                System.out.println("channel.close() error:"+ex.getMessage());
            }
        }
    }

    //转发消息给其它客户(通道)
    private void sendInfoToOtherClients(String msg, SocketChannel self ) throws  IOException{
        System.out.println("服务器转发消息中...");
        System.out.println("服务器转发数据给客户端线程: " + Thread.currentThread().getName());
        //遍历 所有注册到selector 上的 SocketChannel,并排除 self
        for(SelectionKey key: selector.keys()) {
            Channel targetChannel = key.channel();

            //排除自己
            if(targetChannel instanceof  SocketChannel && targetChannel != self) {
                SocketChannel dest = (SocketChannel)targetChannel;
                //将信息存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //将buffer数据写入通道
                dest.write(buffer);
            }
        }
    }

    public static void main(String[] args) {
        //创建服务器对象
        NioServer nioServer = new NioServer();
        nioServer.listen();
    }}

2. クライアント
package com.nezha.guor.nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Scanner;public class NioClient {
    private final int PORT = 8080; //服务器端口
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    public NioClient() throws IOException {
        selector = Selector.open();
        socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //将channel注册到selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(username + " is ok...");
    }

    //向服务器发送消息
    public void sendInfo(String info) {
        info = username + " 说:" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        }catch (IOException e) {
            System.out.println("sendInfo error:"+e.getMessage());
        }
    }

    //读取从服务器端回复的消息
    public void readInfo() {
        try {
            int readChannels = selector.select();
            if(readChannels > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if(key.isReadable()) {
                        //得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        //得到一个Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取
                        sc.read(buffer);
                        //把读到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
                iterator.remove(); //删除当前的selectionKey, 防止重复操作
            } else {
                System.out.println("没有可以用的通道...");
            }
        }catch (Exception e) {
            System.out.println("readInfo error:"+e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        NioClient nioClient = new NioClient();
        new Thread() {
            public void run() {
                while (true) {
                    nioClient.readInfo();
                    try {
                        Thread.currentThread().sleep(2000);
                    }catch (InterruptedException e) {
                        System.out.println("sleep error:"+e.getMessage());
                    }
                }
            }
        }.start();

        //发送数据给服务器端
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            nioClient.sendInfo(scanner.nextLine());
        }
    }}

3. コンソール出力

Java NIO サンプルの使用法と特性を分析する
Java NIO サンプルの使用法と特性を分析する


以上がJava NIO サンプルの使用法と特性を分析するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。