Java非閉塞I/Oの使い方


ほとんどの知識と実例はO'REILLYの『Javaネットワークプログラミング』(Java Network Programming、Fourth Edition、by Elliotte Rusty Harold(O'REILLY)から来ています。
渋滞しないI/O概要
非閉塞I/O(NIO)は高合併を処理する手段である。高合併の場合は、スレッドの作成と回収およびオンライン・プロセス間の切り替えのためのオーバヘッドが無視できなくなり、このときは、非ブロッキングI/O技術が使用される。この技術の核心思想は毎回準備されたコネクションを選んで、できるだけ早くこのコネクションで管理できるだけ多くのデータを充填して、次の準備されたコネクションに転向します。
ブロックされていないI/Oを利用したクライアント
一般的に、クライアントは高数の同時接続を処理する必要がありません。実際には、非ブロッキングI/Oは主にサーバのために設計されていますが、クライアントでも使用できます。クライアントのデザインはサーバよりも簡単ですので、クライアントを使って簡単なプレゼンテーションを行います。
まず、チャンネルとバッファエリアを紹介します。非閉塞I/Oでは、SocketChanel類を使って接続を作成します。SocketChannelオブジェクトを取得するには、SocketAddresオブジェクト(通常はそのサブクラスInetSocketAddressを使用します)をその静的な工場方法openに導入する必要があります。以下の例を示します。

SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
SocketChannel client = SocketChannel.open(address);
open()メソッドはブロックされていますので、その後のコードは接続が確立されるまで実行されません。接続が確立されないと、IOExceptionの異常が発生します。
接続が確立されたら、入力と出力を取得する必要があります。従来のgetInputStream()とは違って、getOutputStream()を利用して、チャネル自体に直接書き込むことができます。バイト配列を書き込むのではなく、ByteBufferオブジェクトを書き込みます。ByteBufferオブジェクトはByteBuffer.allocate(int capacity)で取得し、capacityはバッファサイズ、単位はバイト:

ByteBuffer buffer = ByteBuffer.allocate(74);
ByteBufferオブジェクトを取得した後、SocketChanelオブジェクトに渡すread方法では、SocketChanelオブジェクトは、このバッファ領域にSocketから読み取られたデータで充填されます。read()メソッドは、読み込みに成功し、バッファに格納されたバイト数を返します。デフォルトでは、少なくとも1バイトを読み取ったり、-1に戻ってデータが終了したことを示し、バイトが利用可能でない場合はブロックされます。これはInputStreamの行動とほぼ同じです。ただし、非ブロッキングモードに設定した場合、バイトがないとすぐに0に戻り、ブロックされません。
今はバッファ内にいくつかのデータがあると仮定して、後でそれらを取り出す必要があります。従来の方法では、まず1バイトの配列にデータを書き込んでから出力ストリームに書き込みます。ここでは、チャネルに完全に基づく方法を紹介します。Chanelsツールクラスを利用して、出力ストリームを一つのチャネルにカプセル化します。

WritableByteChannel out = Channels.newChannel(System.out);
上のコードはSystem.outを一つの通路に封入します。これから出力できます。ByteBufferオブジェクトは出力する前に、チャンネルを先頭から読むようにflipメソッドを呼び出す必要があります。読み終わったら、そのclearメソッドを呼び出して、バッファの状態をリセットします。次は一回のデータ出力を行うコードです。

buffer.flip();
out.write(buffer);
buffer.clear();
例1:非閉塞I/Oを利用して実現されたCharGeneratorクライアント
サーバコード:

public static void createCharGeneratorServer(){
  try(ServerSocket server = new ServerSocket(19)){
    while(true){
      try(Socket connection = server.accept()){
        OutputStream out = connection.getOutputStream();
        int firstPrintableCharacter = 33;
        int numberOfPrintableCharacter = 94;
        int numberOfCharactersPerLine = 72;
        int start = firstPrintableCharacter;
        while(true){
          for(int i = start ;
              i < start + numberOfCharactersPerLine ; i++){
            out.write
            (firstPrintableCharacter + (i - firstPrintableCharacter) % numberOfPrintableCharacter);
          }
          out.write('\r');
          out.write('
'); start = firstPrintableCharacter + (start + 1 - firstPrintableCharacter) % numberOfPrintableCharacter; } }catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } }
クライアントコード:

try {
  SocketAddress address = new InetSocketAddress("127.0.0.1", 19);
  SocketChannel client = SocketChannel.open(address);
  ByteBuffer buffer = ByteBuffer.allocate(74);
    WritableByteChannel out = Channels.newChannel(System.out);
  while(client.read(buffer) != -1){
    buffer.flip();
    out.write(buffer);
    buffer.clear();
  }
} catch (IOException e) {
  e.printStackTrace();
}
  (    ):
]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEF
^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFG
_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGH
`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHI
abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ
bcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK
非ブロッキングモードを有効にする
上のプログラムと入出力ストリームを使う従来の方式は大差がありません。ただし、ServerSocketを呼び出すことができるconfigreBlocking(false)方法は、非ブロッキングモードに設定されている。このモードでは、利用可能なデータがなければ、read()方法はすぐに戻ってきます。これにより、クライアントは他のことをすることができます。ただし、read()方法はデータが読めない時は0に戻りますので、データを読み込むサイクルはいくつかの変更が必要です。

while(true){
  //              ,         
  int n = client.read(buffer);
  if(n > 0){
    buffer.flip();
    out.write(buffer);
    buffer.clear();
  }else if (n == -1) {
    //       ,      
    break;
  }
}
締め括りをつける
以上がJava非閉塞I/Oの使い方のすべてです。ご協力をお願いします。いらっしゃいませ。Javaネットワークプログラミングの基本編の一方向通信、  Javaは、プロキシを使用してネットワーク接続方法の例を示す。など、何か問題があったらいつでもメッセージを残してください。皆さん、当駅を応援してくれてありがとうございます。