理系学生日記

おまえはいつまで学生気分なのか

Socket

最近ソケット通信実装のサポートに入っていたので、ちょっとまとめてみたいと思います。

Socket からのデータ読込

Java に関しては不勉強なところが多いんですが、Socket クラスは「ソケット」を抽象化したものであり、「ソケット」は TCP におけるコネクションの端点を抽象化したものです。 結果として、Socket クラスには、TCP における端点に必要な機能が抽象化されています。

一般に Java で Socket から生のバイトを読み込む場合はこういうコードになると思います。これは TCP のコネクションを都度接続する前提です。

byte[] buf = new byte[tlgrmSize];
InputStream is = new BufferedInputStream(socket.getInputStream());

// ByteArrayOutputStream baos = new ByteArrayOutputStream(tlgrmSize);
while((len = is.read(buf, 0, remaining)) != -1) {

    // do something.
    // ex., baos.write(buf, 0, len);

    remaining = hogehoge() // 読み込むべき残りバイトを計算
}

つまりは、以下のような事柄を、InputStream#read で -1 (EOF) が返ってくるまで続けます。 読み込むべき残りバイトを計算するか否かは要件に依存します。

  1. 受信用の byte[] バッファを生成
  2. Socket#getInputStream() で得られる InputStreamread メソッドで、データを byte[] バッファに読み込み
  3. byte[] バッファに読み込んだバイトに対して必要な処理を行う (ここでは、ByteArrayOutputStream に書き込んでいます)
  4. 読み込むべき残りバイトを計算する

このあたりの処理フローは、Java だろうが C だろうがあまり変わらないと思います。

注意すべきは、InputStream#read メソッドで、要求したデータサイズが一度に読み込めないケースが多々ある点で、これは考慮しておかなければなりません。 上記コードですと、InputStream#read の第三引数で指定した「読み込む最大バイト数」よりも実際に読み込んだバイト数 (read の返却値) が小さい場合があります。

バッファサイズとウィンドウサイズ

上記の受信用バッファのサイズをどのくらい取れば良いのか、という話もあったのですが、このサイズは正直、あまりに大きなメモリ負荷を与えないのならどのくらいでも良いと思っています。 大した流量もなく、読み込むべきデータサイズが既知であり、かつ、それが数 KB に収まるなら、そのサイズくらい一気に取ってしまえば良いんじゃないですかね。

最初に見たコードは TCP のウィンドウサイズを意識したバッファサイズ設計になっていたというのが上の話に繋がるんですが、 Java の Socket において TCP のウィンドウサイズを意識する必要はないと思います。そもそも、TCP のウィンドウサイズの情報は Socket からは入手できないよね。

TCP のウィンドウサイズは大きく 2 つ存在していて、これらのサイズは通信状況に依って動的に調整されます。

  • フロー制御におけるウィンドウサイズ
    • TCP において、受信側の受信バッファをオーバーフローさせないためのウィンドウサイズ。
  • 輻輳制御におけるウィンドウサイズ

どのタイムスライスにおいて、どの程度のデータを送られ得るかは、両ウィンドウやその他の状況によって複雑に調整されます。この手の情報を抽象化するのは Kernel の役割です。