追記
POSIX では明確にソケットの最大値とはうたってはいないものの、Linux の実装を見ても最大値と扱う方が良い様です。また Winsock では select(2)
の第一引数は無視されるようです。
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_array
がFD_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_SETSIZE
はfd_array
の個数を定義するマクロであり、FD_ZERO/FD_CLR/FD_SET
でそれを操作する再に用いられる閾値であり、ループ回数なのです。さらにFD_ZERO/FD_CLR/FD_SET
はFD_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.aspxFour 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にも移植するかもしれないソフトウェアならば、ディスクリプタの値がディスクリプタ集合の最大個数である...といった様なコーディングは辞めましょう。