2009/01/14

Recent entries from same category

  1. RapidJSON や simdjson よりも速いC言語から使えるJSONライブラリ「yyjson」
  2. コメントも扱える高機能な C++ 向け JSON パーサ「jsoncpp」
  3. C++ で flask ライクなウェブサーバ「clask」書いた。
  4. C++ 用 SQLite3 ORM 「sqlite_orm」が便利。
  5. zsh で PATH に相対パスを含んだ場合にコマンドが補完できないのは意図的かどうか。

追記

POSIX では明確にソケットの最大値とはうたってはいないものの、Linux の実装を見ても最大値と扱う方が良い様です。また Winsock では select(2) の第一引数は無視されるようです。

C言語でソケットを使うプログラミングを行う際、ソケットディスクリプタがシグナル状態かを調べる方法としてselect(2)があります。
使い方は int r;
fd_set rfds;

FD_ZERO(&rfds);
FD_SET(sock, &rfds);

r = select(1, &rfds, NULL, NULL, NULL);
といった感じ。ここでselect(2)の第一引数に渡している値は、ディスクリプタ集合rfdsの内、いくつ検証するかを指す値。つまりrfdsに対してFD_ZERO/FD_CLRしてからFD_SETした回数となります。
ちなみに戻り値は、ディスクリプタ集合の内どれだけシグナル状態かの数が返ります。つまり r = select(num_fds, &rfds, &wfds, &efds, NULL);
の場合、rfds/wfds/efdsの内、シグナル状態であるディスクリプタの総数が返ります。

昔のC言語で書かれたソースを見ると、よく int r;
fd_set rfds;

FD_ZERO(&rfds);
FD_SET(sock, &rfds);

r = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
FD_SETSIZEを使って書かれた記述を見ます。これはrfdsが構造体であり、そのメンバに持つディスクリプタ格納配列fd_arrayFD_SETSIZEでサイズ定義されている事を利用している為です。
これを無駄と考える人がいた為か、こういう記述も未だに良く見かけます。
int r;
fd_set rfds;

FD_ZERO(&rfds);
FD_SET(sock1, &rfds);
FD_SET(sock2, &rfds);
int check_sock = max(sock1, sock2);

r = select(max(sock1, sock2), &rfds, NULL, NULL, NULL);
確かにFD_SETSIZEの理屈から言えば納得が行く話かも知れませんが、FD_SETSIZEの定義とはfd_arrayの個数でありディスクリプタの値が取り得る値の最大値ではありません。ディスクリプタが順列で生成されるなんて仕様もありません。
つまり、間違いです。

にも関わらず、ディスクリプタの最大値はFD_SETSIZEだと思わせる記述が出回ってしまったのだと思います。
Manpage of SELECT_TUT

nfds: 全ての集合に含まれるファイルディスクリプタのうち、値が最大のものに 1 を足した整数である。すなわち、ファイルディスクリプタを集合に加える作業の途中で、全てのファイルディスクリプタを見て最大値を求め、それに 1 を加えて nfds として select に渡さないといけない、ということだ。

http://www.linux.or.jp/JM/html/LDP_man-pages/man2/select_tut.2.html
おそらく初期のディスクリプタの実装が配列の添え字であった為、こういう記述となり残っていったのだと思う。

例えばWindowsで言えば、socket(7)関数で返るディスクリプタの値はFD_SETSIZEに収まらない値で返ります。またMinGW(Minimalist GNU for Windows)のwinsock.hで定義されているFD_SETSIZEは64と中途半端な値になっています。
それはなぜか...
適当だからです。

通常、1プロセスが扱えるディスクリプタの数が設定されるべきですが、それが明確に定義すべきでない環境では意味のない値になったのだと思います。
しかしながら、多数のディスクリプタ(ファイルもソケットも)を扱うプログラムならば64個使い切ってしまう事はあり得りえるでしょうね。
ではどうすれば良いか。FD_SETSIZEfd_arrayの個数を定義するマクロであり、FD_ZERO/FD_CLR/FD_SETでそれを操作する再に用いられる閾値であり、ループ回数なのです。さらにFD_ZERO/FD_CLR/FD_SETFD_SETSIZEと同じくマクロなのです。 #define FD_CLR(fd,set) do { u_int __i;\
for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
       if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
       while (__i < ((fd_set *)(set))->fd_count-1) {\
               ((fd_set*)(set))->fd_array[__i] = ((fd_set*)(set))->fd_array[__i+1];\
               __i++;\
       }\
       ((fd_set*)(set))->fd_count--;\
       break;\
       }\
}\
} while (0)
しかもFD_SETSIZEの定義は#ifdefにより使い手側が変更出来る様になっています。もし通常よりも多くディスクリプタを扱いたいならばコンパイル時にFD_SETSIZEを定義してやれば良いのです。
この事は、MSDNにも書いてあります。
select Function (Windows) http://msdn.microsoft.com/en-us/library/ms740141.aspx

Four macros are defined in the header file Winsock2.h for manipulating and checking the descriptor sets. The variable FD_SETSIZE determines the maximum number of descriptors in a set. (The default value of FD_SETSIZE is 64, which can be modified by defining FD_SETSIZE to another value before including Winsock2.h.)

ディスクリプタ集合をを操作/チェックするためにヘッダーファイルWinsock2.hに4つのマクロが定義されています。FD_SETSIZEはディスクリプタ集合の最大個数を記述子の最大数を決定します。 (FD_SETSIZEの初期値は64です。これはwinsock2.hをインクルードする前に別の値で変更する事が出来ます。)


但し、このFD_SETSIZEが少ない値のままコンパイルされたライブラリと、多く設定した値のライブラリを併用すると場合によっては誤動作する可能性があるので注意が必要です。まぁこれはUNIXでも同じ話ですね。

だらだら書きましたが、何を言いたいかというと

UNIXで開発していて、将来的にWindowsにも移植するかもしれないソフトウェアならば、ディスクリプタの値がディスクリプタ集合の最大個数である...といった様なコーディングは辞めましょう。

という事です。
Posted at by | Edit