2018年2月23日金曜日

Java 7以降を使う場合の、ソケット通信の実装の仕方

Java 7以降でソケット通信を実装する場合、大きく3つのやり方がある。

  1. java.net.socket を使う方法
  2. java.nio.channel.ServerSocketChannel を使う方法
  3. java.nio.channel.AsynchronousServerSocketChannel を使う方法

java.net.socket は最も古典的な方法で、入出力操作(接続の待ち受けや送受信処理)がブロッキングで行われる。つまり、それらの処理をしている間、他のことができない。
自前でスレッドをたてれば並行に処理できるのだけど、スレッド管理はオーバヘッドを伴うので、多くの接続をさばくようなサーバプログラムの場合、スレッドを濫用するのは望ましくない。

java.nio.channel.ServerSocketChannel は、J2SE 1.4から導入されたNew I/O(NIO)のAPI。入出力操作についてブロッキングとノンブロッキングのどちらかを選んで使うことができる。
ノンブロッキングの場合、セレクタ(java.nio.channels.Selector)という分配器みたいなオブジェクトを使う。入出力操作をするメソッドは完了を待たずに(=ブロックせずに)returnし、セレクタに問い合わせることで入出力操作の結果を受け取れる、というイメージ。

java.nio.channel.AsynchronousServerSocketChannel は、Java SE7から導入されたNIO.2のAPI。ServerSocketChannelと似た作りだが、ノンブロッキングでの入出力操作がしやすいよう、より強化されている。


java.nio.channel.AsynchronousServerSocketChannel を使う場合、入出力操作の結果の受け取り方には大きく2つの方法がある。
1つ目は、コールバック。つまり、入出力操作の呼び出し時に、コールバック関数を登録し、処理完了時にそちらが呼び出される。
2つ目は、Futureオブジェクトを使う方法。入出力操作を呼び出した際に、返り値としてFutureクラスのオブジェクトを受け取る。このFutureオブジェクトに対して、任意のタイミングでアクセスすることで、結果(あるいは結果が出る前の状態)を取得できる。

例えば accept メソッドは以下2パターン用意されており、前者が上記2つ目、後者が上記1つ目に対応する。

  • Future<AsynchronousSocketChannel> accept()
  • <A> void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler)


というわけで、ノンブロッキングかつリアルタイムに処理を進めたいのであれば、java.nio.channel.AsynchronousServerSocketChannel のコールバックの仕組みを使うのが良さそう。
具体的な使い方は、JavaDocを参照: https://docs.oracle.com/javase/jp/8/docs/api/java/nio/channels/AsynchronousServerSocketChannel.html

上記acceptメソッド(コールバック版)にて、CompletionHandlerの型引数にAsynchronousSocketChannelが指定されている。コールバック関数側は、「接続待ち」の結果として、このAsynchronousSocketChannelオブジェクト(=接続相手との通信チャネル)を受け取る。


AsynchronousSocketChannelクラスのreadメソッドは、接続相手からの通信パケットを読み出す操作である。これもノンブロッキングで呼び出せるようになっており、上記と同様、Futureを使うパターンと、コールバックを使うパターンとがある。
readメソッドには以下の4種類が存在し、1つ目がFutureパターン、2つ目以降がコールバックパターンである。

  • Future<Integer> read(ByteBuffer dst)
  • <A> void read(ByteBuffer[] dsts, int offset, int length, long timeout, TimeUnit unit, A attachment, CompletionHandler<Long,? super A> handler)
  • <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer,? super A> handler)
  • <A> void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler)

3つ目と4つ目の違いは、タイムアウト関連のパラメータの有無。タイムアウトが指定されていると、一定時間経過後、読み取り処理はInterruptedByTimeoutExceptionで終了する。

2つ目のものは、やや特殊で、タイムアウト関連パラメータの他、offsetとlengthを指定できるようになっている。また、dstsはバイトバッファの配列になっている。
配列内の、offsetで指定した位置以降の各バッファに、順にバイトが読み込まれていく。例えば、読み取り対象が計20バイトで、配列dstsの各要素の長さが8、offsetが2の場合は、以下のようになる。
dsts[0] : 無し
dsts[1] : 無し
dsts[2] : 1~8バイト目まで
dsts[3] : 9~16バイト目まで
dsts[4] : 17~20バイト目まで
lengthは、アクセスされるバッファ数の上限。上記のケースでlengthが2であれば、dsts[4]にはバイトが読み込まれないことになる。
この方式は、複数の固定長ヘッダがつらなるようなメッセージを読み取る時に役に立つことがあるらしい。(と言っても、後から分割しなくて済むという程度か。)

2018年2月11日日曜日

IntelliJ IDEAでターミナル⇔エディタ間移動のショートカット

最近、IntelliJ IDEAに乗り換えた。

ツールウィンドウとしてターミナルを出しておけるので、そこからgitコマンドを打ったりしている。

このターミナルと、エディタ(=現在のタブ)との間を、キーボードショートカットで行ったり来たりできるようにしたい。

デフォルトでは、「Alt+F12」でエディタ⇒ターミナルの移動ができる。
のだが、ターミナル⇒エディタの移動はひと工夫しないとできない様子。

※参考:https://stackoverflow.com/questions/21134438/how-to-jump-from-intellij-terminal-to-editor-with-shortcut/23860667

以下のような方策があるらしい。
・もう一度「Alt+F12」を押す(エディタに移動できるが、ターミナルは閉じてしまう)
・「Alt+2」を2回押す(ターミナルに「2」が入力されてしまうことがある)
・「Alt+Home」でナビゲーションバーに移動した後、「ESC」でエディタに移動
・File⇒Settings⇒Tools⇒Terminal⇒Override IDE shortcuts、のチェックを外した上で、「ESC」を使う

ターミナルは開いたままにしておきたいし、ワンアクションで移動したいので、ひとまず上記4つ目の方法を選択。
しばらく使ってみて様子を見る。