[index]
[top]
[bottom]

java.nio

javaで、性能を要求されたネットワーク処理を書くときに、避けて通れないのが、java.nioパッケージです。特に、このパッケージを使った非ブロックモードのサーバは、多くのリクエストを同時に処理する必要があるサーバの場合、必須の機能といえるでしょう。

しかし、どちらかというとUNIXのシステムコールに似たインターフェイスで、少々とっつきにくいパッケージではあります。

その辺を調べる機会があったので、備忘録も兼ねて公開します。

まずはサーバ側の簡単なサンプル

面倒なことは何も考えていないサーバプログラムのサンプルです。クライアントからデータを受信し、何もしないでデータを送信します。データのフォーマットは、先頭4バイトがデータ長のバイトデータです。

さて、このサンプルコードは、どう動いているのでしょう?だいたい次の様に動いています。

クライアント側ネットワーク上サーバ側
SelectorProvider.provider().openSelector();
ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
Selector#select()
(制御をブロック)
Socket#open(host, port) ---SYN-->     ・
<--SYN ACK---     ・
---ACK-->     ・
(ブロックから抜ける)
readySocketChannel.accept();
SocketChannel#register(SelectionKey.OP_READ)
Selector#select()
(制御をブロック)
Socket#write() ---PSH ACK-->     ・
<--ACK---     ・
Socket#read() (ブロックから抜ける)
(制御をブロック) socketChannel.read(buffer);
    ・ readyKey.interestOps(SelectionKey.OP_WRITE);
    ・ Selector#select()
    ・ (制御をブロック)
    ・ (ブロックから抜ける)
    ・ socketChannel.write(buffer);
    ・ <---PSH ACK---
    ・ ---ACK-->
(ブロックから抜ける) socketChannel.close();

このコードは、殆どの場合正しく動作します。

品質の良い社内LANでのみ運用され、全てのコンピュータのネットワークは標準的な設定がなされ、通信相手のプログラムは常識的な処理を行い、そして何より、あなたの運が良ければ、このコードを使用したプログラムは、一般的な寿命をまっとうできるかもしれません。

しかし、最近のシステムでは、通信相手が地球の裏側にいることも当たり前です。

ネットワークの管理などまともに行われていないので、ネットで拾ってきた情報で各自が適当にコンピュータの設定を行っていても、驚くに値しません。

今時のプログラムは、『XX入門』を机一面に広げたプログラマの手によるものも珍しく無いので、スリルがいっぱいです。

そして何より、運の良いプログラマなど、この世に存在しません(これは、マーフィーの法則が証明しています(古い?))。

さあ、どうしましょう?

もちろん、運を天に任せるというのも一つの手ではあります。何があっても笑ってごまかせる場合、これは最良の手かもしれません。

しかし、では、何が問題でしょう。

まずは読み込む時に指定している、バッファのサイズがあります。これは当然ですね、必要なサイズに増やしてください。

しかし、どこまでも増やせば良いのでしょうか?実はある上限値があります。

一つは通信プロトコル上の、上限値です。

PCが接続されている殆どのネットワークは、イーサネットとTCP/IPの組み合わせで構成されていますが、この構成の場合の上限は1460バイトになります。

←--20バイト--→←--------------最大1460バイト---------------→
←--20バイト--→TCPヘッダTCPデータ
IPヘッダIPデータ
FremeヘッダFrameデータ(最大1500バイト)FCS

上の図で、TCPデータの部分が実際に送ることのできるデータです。イーサネットを使用した場合のフレームデータの上限が1500バイトで、IPヘッダとTCPヘッダにそれぞれ最低20バイト必要なため、残りの1460バイトが一度に送信できる最大値となります。

クライアントがサーバに向けて、2000バイトのデータを送った場合は、次の様になります。

クライアント側ネットワーク上サーバ側
Socket#write(2000byte) ---(データ1460byte) ACK-->     ・(A)
<--ACK---     ・
---(データ540byte) PSH ACK-->     ・(B)
<--ACK---     ・
(ブロックから抜ける)
socketChannel.read(buffer);

PSHフラグが立っているパケットは、受信した際に必ず上位アプリケーションに通知されます。しかし、PSHフラグの立っていないパケットに関しては、アプリケーションに通知されないことが決まっている訳ではありません。

もしも(A)の時点で、受信したデータが上位アプリケーションに通知されたらどうなるのでしょう?

先に示した例では、(B)で受信したデータを、取りこぼしてしまうことになります。

必ず、必要なサイズを読み込んでから、書き込みを行わなければいけません。そのための先頭4バイトです。

このあたりは、二つ目の上限値に関連していて、ネットワークカードやドライバにもそれぞれバッファがあるため、ここがいっぱいになると、PSHフラグの有無に関係なく、上位アプリケーションに通知されます。