NIO2非同期ソケットチャネルの手引き
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プロジェクト]からアクセスできます。