JavaネットワークプログラミングのNIO(セレクター)

                    この記事は進歩的なものにすることを目的としていますが、最後の記事だけを読んでください

 

 セレクターの紹介

  【1】セレクターの作成

             セレクター セレクター = Selector.open();

  【2】セレクターに登録されたチャンネル

               まず、チャネルはノンブロッキングでなければなりません

                channel.register(selector, type of operation, bound component); 選択キーを返します             

1) チャネルが登録された後、チャネルが特定の準備完了状態になると、セレクターによって照会できます。
この作業は、セレクターの select() メソッドを使用して行われます。関心のある select メソッドの役割
準備状況を照会するための興味深いチャネル操作。
2) セレクターは、チャネルで発生している操作の準備状況を継続的に照会できます。興味のあるものを選択
操作の準備が整った状態。チャネルの操作の準備が整い、セレクターにとって重要な操作になると、
Selector によって選択され、 セレクション キー コレクション に入れられます。
3) セレクターに登録されたチャネル操作のタイプを最初に含む選択キー。たとえば、
SelectionKey.OP_READ. また、特定のチャネルと特定のセレクターの間の登録関係も含まれます。
アプリケーションを開発する場合、選択キーはプログラミングの鍵です。NIO のプログラミングは、対応する選択キーに従って操作を実行することです。
異なるビジネス ロジック処理。
4) 選択キーの概念は、イベントの概念と似ています。リスナーモードと同様の選択キー
イベント。Selector はイベント トリガー モードではなく、アクティブなクエリ モードであるため、イベントとは呼ばれません。
イベントですが、SelectionKey 選択キーと呼ばれます。

【3】ポーリングクエリレディ操作

1 ) Selector select () メソッドを介して 、準備ができているチャネル操作を照会でき、これらの準備ができています
状態コレクションは、要素が SelectionKey オブジェクトである Setコレクションに格納されます
2 ) Selector の オーバーロードされたクエリ select() メソッドのいくつかを次に示します。
           - select(): 登録したイベントで少なくとも 1 つのチャンネルの準備が整うまでブロックします。
           - select(long timeout) : select()と同じです が、最長のブロッキング イベントは timeout ミリ秒です。
           - selectNow(): ノンブロッキング、チャンネルの準備ができている限りすぐに戻ります。
select()メソッド によって返される int値は 、以前の 選択以降、より正確には、いくつのチャンネルが準備できているかを示します
メソッドと 選択メソッド の間の期間に準備完了になったチャネルの数。
例: select() メソッドが初めて呼び出され、チャネルが準備完了になると 1 が返され、再度呼び出されると 1 が返されます。
別のチャネルの準備ができている場合、このメソッドは再び 1 を返します。最初の準備ができたら
channel は何もしません。これで 2 つのチャネルの準備が整いましたが、各 select() メソッド呼び出しの間に、
準備ができているチャネルは 1 つだけです。

 【4】選考中止方法

セレクターは選択プロセスを実行します, システムの最下層は各チャンネルが順番に準備ができているかどうかを尋ねます . このプロセスにより, 呼び出し元のスレッドがブロックされた状態になることがあります. 次に, ブロックされたスレッドをウェイクアップする次のメソッドがあります. select() メソッド。
        wakeup() メソッド : Selector オブジェクトの wakeup() メソッドを呼び出すと、ブロックされた状態の select() メソッドがすぐに復帰します. このメソッドは、復帰していないセレクタの最初の選択操作をすぐに復帰させます. 現在進行中の選択操作がない場合、次に select() メソッドを呼び出すとすぐに戻ります。
        close() メソッド : close() メソッドを介してセレクターを閉じます. このメソッドは、選択操作でブロックされたすべてのスレッドを起こし (wakeup() と同様)、同時に、セレクターに登録されているすべてのチャネルを登録解除します。すべてのキーはキャンセルされます が、チャネル自体は閉じられません

NIO プログラミング手順

ステップ 1: セレクター セレクターを作成する
ステップ 2: ServerSocketChannel チャネルを作成し、リスニング ポートをバインドする
ステップ 3: Channel チャネルをノンブロッキング モードに設定する
ステップ 4: Channel を Socketor セレクターに登録し、接続イベントをリッスンする
ステップ 5: Selector の select メソッド (サイクリック コール) を呼び出して、チャネルの準備状況を監視する
ステップ 6: selectKeys メソッドを呼び出して、準備完了のチャネル コレクションを取得する
ステップ 7: 一連の準備完了チャネルを走査し、準備完了イベントのタイプを判別して、特定のビジネス オペレーションを実装する
ステップ 8: 業務に応じて、監視イベントを再登録する必要があるかどうかを判断し、ステップ 3 を繰り返します。

コード 1.0

public class SelectorServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        //创建selector选择器
        Selector selector = Selector.open();
        //将ssc注册到选器器(建立两者的联系)
        SelectionKey sscKey = ssc.register(selector, 0, null);
        //选择哪种监听的事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                System.out.println("key::"+key);
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                SocketChannel sc = channel.accept();
                sc.configureBlocking(false);
                SelectionKey scKey = sc.register(selector, 0, null);
                System.out.println("scKey---->"+scKey);
                scKey.interestOps(SelectionKey.OP_READ);
                System.out.println("sc已经在selector中注册了!");
            }
        }
    }
}

 結果分析:

     

 解決

[1] selector.select() をトリガーするイベントを区別する

[2] イベントを処理した後、対応する登録キー セットでイベントを削除します。

 コード 2.0

public class SelectorServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();//解决处理后事件在set集合中还有的现象
                if (key.isAcceptable()){//区分不同事件触发的结果
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    System.out.println("scKey---->"+scKey);
                    scKey.interestOps(SelectionKey.OP_READ);
                    System.out.println("sc已经在selector中注册了!");
                }else if (key.isReadable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(32);
                    int len = channel.read(buffer);
                    buffer.flip();
                    System.out.println(StandardCharsets.UTF_8.decode(buffer).toString());
                    buffer.clear();
                }


            }
        }
    }
}

 新しい質問 ByteBuffer によって割り当てられたスペースが十分でない場合はどうなりますか (分析)

       読み取りが完了していない場合、次の読み取りがトリガーされます。

 解決策: クライアントに接続されている各チャネルの ByteBuffer スペースを動的に拡張します (これにより、固着や半パッキングの状況を回避できます!)

 新しい質問:

クライアントが強制的にシャットダウンされると、サーバーが停止します

 クライアントが正常終了すると、サーバーは無限ループに入る

解決:

クライアントは強制的にシャットダウンされ、サーバーは例外を報告します。try--catch ステートメント key.cancel() を使用して何もしません。

クライアントは正常に終了し、クライアントはサーバーにデータを送信します。read = -1;

コード 3.0 (最終版)

public class SelectorServer {
    
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();//解决处理后事件在set集合中还有的现象
                if (key.isAcceptable()){//区分不同事件触发的结果
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    System.out.println("scKey---->"+scKey);
                    ByteBuffer buffer = ByteBuffer.allocate(4);
                    scKey.attach(buffer);//为每一个注册到set集合中的channel分配独立的缓冲区
                    scKey.interestOps(SelectionKey.OP_READ);
                    System.out.println("sc已经在selector中注册了!");
                }else if (key.isReadable()){
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = channel.read(buffer);
//                    System.out.println("read::"+read);
//                    System.out.println("positon:"+buffer.position()+"limit:"+buffer.limit());
                        System.out.println(buffer);
                        if (read == -1){//客户端正常结束,read的值等于-1
                            key.cancel();
                            continue;
                        }
                        if(read == buffer.capacity()){
                            ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
                            newBuffer.flip();
                            newBuffer.put(buffer);
                            key.attach(newBuffer);
                        }else{
                            buffer.flip();
                            System.out.println(StandardCharsets.UTF_8.decode(buffer).toString());
                            buffer.clear();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }

                }


            }
        }
    }
}

結果:

クライアント正常終了

 クライアント強制終了

 

 セレクター書き込みイベントは読み取りイベントに似ています

サーバ

public class SelectorWriter {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT, null);

        while (true){
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()){
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey sckey = sc.register(selector, SelectionKey.OP_READ);

                    //发送大量的数据
                    StringBuilder str = new StringBuilder();
                    for (int i = 0; i < 300000; i++) {
                        str.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(str.toString());
                    int write = sc.write(buffer);
                    System.out.println(write);
                    if (buffer.hasRemaining()){
                        // 4. 关注可写事件
                        sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);
                       // sckey.interestOps(sckey.interestOps() | SelectionKey.OP_WRITE);
                        // 5. 把未写完的数据挂到 sckey 上
                        sckey.attach(buffer);
                    }

                }else if(key.isWritable()){
                    SocketChannel sc = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    int write = sc.write(buffer);
                    System.out.println(write);
                    //清理工作
                    if (!buffer.hasRemaining()){
                        key.attach(null);
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                    }
                
                }
            }
        }
    }
}

 クライアント

public class WriterClient {
    public static void main(String[] args) throws Exception {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8080));

        // 3. 接收数据
        int count = 0;
        while (true) {
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            count += sc.read(buffer);
            System.out.println(count);
            buffer.clear();
        }
    }
}

おすすめ

転載: blog.csdn.net/qq_57533658/article/details/130004236