1概要

この記事では、Java 7 NIO.2チャネルAPIを使用して単純なサーバーとそのクライアントを構築する方法を説明します。

それぞれ、サーバーとクライアントの実装に使用される重要なクラスである

AsynchronousServerSocketChannel

クラスと

AsynchronousSocketChannel

クラスを見てみましょう。

NIO.2チャンネルAPIに慣れていない場合は、このサイトに紹介記事があります。あなたはこのリンクをたどることによってそれを読むことができます:/java-nio-2-async-channels[link]。

NIO.2チャネルAPIを使用するために必要なすべてのクラスは、

java.nio.channels

パッケージにまとめられています。

import java.nio.channels.** ;


2

Future


を持つサーバー


AsynchronousServerSocketChannel

のインスタンスは、そのクラスで静的オープンAPIを呼び出すことによって作成されます。

AsynchronousServerSocketChannel server
  = AsynchronousServerSocketChannel.open();

新しく作成された非同期サーバーソケットチャネルは開いていますがまだバインドされていないので、ローカルアドレスにバインドし、オプションでポートを選択する必要があります。

server.bind(new InetSocketAddress("127.0.0.1", 4555));

ローカルアドレスを使用して任意のポートにバインドするように、nullを渡すこともできます。

server.bind(null);

バインドされると、

accept

APIを使用してチャンネルのソケットへの接続の受け入れを開始します。

Future<AsynchronousSocketChannel> acceptFuture = server.accept();

非同期チャネル操作の場合と同様に、上記の呼び出しはすぐに戻り、実行が継続されます。

次に、

get

APIを使用して

Future

オブジェクトからの応答を問い合わせることができます。

AsynchronousSocketChannel worker = future.get();

この呼び出しは、クライアントからの接続要求を待つために必要ならばブロックします。永遠に待ちたくない場合は、オプションでタイムアウトを指定できます。

AsynchronousSocketChannel worker = acceptFuture.get(10, TimeUnit.SECONDS);

上記の呼び出しが戻って操作が成功したら、ループを作成してその中で着信メッセージをリッスンし、それらをクライアントにエコーバックできます。


runServer

という名前のメソッドを作成しましょう。このメソッド内で、待機し、受信メッセージを処理します。

public void runServer() {
    clientChannel = acceptResult.get();
    if ((clientChannel != null) && (clientChannel.isOpen())) {
        while (true) {
            ByteBuffer buffer = ByteBuffer.allocate(32);
            Future<Integer> readResult  = clientChannel.read(buffer);

           //perform other computations

            readResult.get();

            buffer.flip();
            Future<Integer> writeResult = clientChannel.write(buffer);

           //perform other computations

            writeResult.get();
            buffer.clear();
        }
        clientChannel.close();
        serverChannel.close();
    }
}

ループ内では、操作に応じて読み書きするバッファを作成するだけです。

その後、読み取りまたは書き込みを行うたびに、他のコードの実行を続けることができます。結果を処理する準備ができたら、

Future

オブジェクトに対して__get()APIを呼び出します。

サーバーを起動するには、そのコンストラクターを呼び出し、次に

main

内の

runServer

メソッドを呼び出します。

public static void main(String[]args) {
    AsyncEchoServer server = new AsyncEchoServer();
    server.runServer();
}


3

CompletionHandler


を持つサーバー

このセクションでは、

Future

アプローチではなく

CompletionHandler

アプローチを使用して同じサーバーを実装する方法について説明します。

コンストラクター内で、

AsynchronousServerSocketChannel

を作成し、以前と同じ方法でローカルアドレスにバインドします。

serverChannel = AsynchronousServerSocketChannel.open();
InetSocketAddress hostAddress = new InetSocketAddress("localhost", 4999);
serverChannel.bind(hostAddress);

次に、それでもコンストラクター内に、クライアントからの着信接続を受け入れるwhileループを作成します。このwhileループは、クライアントとの接続を確立する前にサーバーが終了するのを防ぐために厳密に使用されます。

  • ループが無限に実行されるのを防ぐために、入力接続が標準入力ストリームから読み取られるまで実行をブロックするために、最後に

    System.in.read()

    を呼び出します。

while (true) {
    serverChannel.accept(
      null, new CompletionHandler<AsynchronousSocketChannel,Object>() {

        @Override
        public void completed(
          AsynchronousSocketChannel result, Object attachment) {
            if (serverChannel.isOpen()){
                serverChannel.accept(null, this);
            }

            clientChannel = result;
            if ((clientChannel != null) && (clientChannel.isOpen())) {
                ReadWriteHandler handler = new ReadWriteHandler();
                ByteBuffer buffer = ByteBuffer.allocate(32);

                Map<String, Object> readInfo = new HashMap<>();
                readInfo.put("action", "read");
                readInfo.put("buffer", buffer);

                clientChannel.read(buffer, readInfo, handler);
             }
         }
         @Override
         public void failed(Throwable exc, Object attachment) {
            //process error
         }
    });
    System.in.read();
}

接続が確立されると、accept操作の

CompletionHandler



completed

コールバックメソッドが呼び出されます。

その戻り型は

AsynchronousSocketChannel

のインスタンスです。サーバソケットチャネルがまだ開いている場合は、同じハンドラを再利用しながら、もう一度

accept

APIを呼び出して別の着信接続の準備をします。

次に、返されたソケットチャネルをグローバルインスタンスに割り当てます。次に、それがnullではないこと、そして操作を実行する前に開いていることを確認します。

読み書き操作を開始できる時点は、

accept

操作のハンドラーの

completed

コールバックAPIの内部です。このステップは、チャネルを

get

APIでポーリングした以前のアプローチに代わるものです。

明示的に閉じない限り、

接続が確立された後で

サーバーはもう終了しません。

また、読み取り操作と書き込み操作を処理するために別の内部クラスを作成したことにも注意してください。

ReadWriteHandler

。この時点で、アタッチメントオブジェクトがどのように役立つのかがわかります。

まず、

ReadWriteHandler

クラスを見てみましょう。

class ReadWriteHandler implements
  CompletionHandler<Integer, Map<String, Object>> {

    @Override
    public void completed(
      Integer result, Map<String, Object> attachment) {
        Map<String, Object> actionInfo = attachment;
        String action = (String) actionInfo.get("action");

        if ("read".equals(action)) {
            ByteBuffer buffer = (ByteBuffer) actionInfo.get("buffer");
            buffer.flip();
            actionInfo.put("action", "write");

            clientChannel.write(buffer, actionInfo, this);
            buffer.clear();

        } else if ("write".equals(action)) {
            ByteBuffer buffer = ByteBuffer.allocate(32);

            actionInfo.put("action", "read");
            actionInfo.put("buffer", buffer);

            clientChannel.read(buffer, actionInfo, this);
        }
    }

    @Override
    public void failed(Throwable exc, Map<String, Object> attachment) {
       //
    }
}


ReadWriteHandler

クラスの添付ファイルのジェネリック型はマップです。ここでは、特に2つの重要なパラメータ – 操作の種類(アクション)とバッファを渡す必要があります。

次に、これらのパラメータがどのように使用されるのかを見ていきます。

これがクライアントメッセージにのみ反応するエコーサーバーなので、最初に実行する操作は

read

です。

ReadWriteHandler

s

completed

コールバックメソッド内で、添付データを取得し、それに応じて何をするかを決定します。

それが完了した

read

操作である場合、バッファを取得し、添付ファイルのactionパラメータを変更して、すぐに

write

操作を実行してメッセージをクライアントにエコーします。

それが完了したばかりの

write

操作である場合は、もう一度

read

APIを呼び出して、サーバーが別の受信メッセージを受信できるようにします。


4クライアント

サーバーを設定したら、

AsyncronousSocketChannel

クラスの

open

APIを呼び出してクライアントを設定できます。この呼び出しは、クライアントソケットチャネルの新しいインスタンスを作成し、それを使用してサーバーへの接続を確立します。

AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
InetSocketAddress hostAddress = new InetSocketAddress("localhost", 4999)
Future<Void> future = client.connect(hostAddress);


connect

オペレーションは成功しても何も返しません。ただし、

Future

オブジェクトを使用して非同期操作の状態を監視することはできます。

接続を待つために

get

APIを呼び出しましょう。

future.get()

このステップの後、サーバーへのメッセージ送信とそれに対するエコーの受信を開始できます。

sendMessage

メソッドは次のようになります。

public String sendMessage(String message) {
    byte[]byteMsg = new String(message).getBytes();
    ByteBuffer buffer = ByteBuffer.wrap(byteMsg);
    Future<Integer> writeResult = client.write(buffer);

   //do some computation

    writeResult.get();
    buffer.flip();
    Future<Integer> readResult = client.read(buffer);

   //do some computation

    readResult.get();
    String echo = new String(buffer.array()).trim();
    buffer.clear();
    return echo;
}


5テスト

私たちのサーバーとクライアントアプリケーションが期待通りに動作していることを確認するために、我々はテストを使うことができます:

@Test
public void givenServerClient__whenServerEchosMessage__thenCorrect() {
    String resp1 = client.sendMessage("hello");
    String resp2 = client.sendMessage("world");

    assertEquals("hello", resp1);
    assertEquals("world", resp2);
}


6. 結論

この記事では、Java NIO.2非同期ソケット・チャネルAPIについて説明しました。これらの新しいAPIを使用して、サーバーとクライアントを構築するプロセスを段階的に進めることができました。

この記事の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-io[Githubプロジェクト]からアクセスできます。