(x)inetd も、tcpserver も、最終的には、別の外部プログラムを exec します。 この時、プログラム内部で何が起こっているかを、簡単に列挙すると、
上記の様な事が起こっています。これは、tnetsrv でも同じです。
ネットワークサービスを並行して連続稼動させるためには、fork() に よる子プロセスの生成は、TCP の場合、必要になりますが、その都度 exec するので、OS に負担をかけ、初期のレスポンスが(若干ではあるものの)悪く なります(もちろん、人間にしてみれば、ほんの一瞬ですが...)。
その対処法として、子プロセス生成時に、fork() を使用せずに、vfork() を使用する方法があります。
これは、vfork() した親プロセスは停止させておき、子プロセス側が必ず exec する事で、fork() にかかる負荷を低減させるものですが、ネットワーク サービスの場合、親は親で、子プロセスの生成を行ったならば、直ちに次の 待ち受け動作に入らせたい所です。それに、vfork() を使って低減できるのは、 子プロセスの生成にかかるOSコストだけで、vrofk() を使っても、exec にかかる コストは減らない(はず)です。 (間違っていたらご指摘下さい)
UNIX の場合、「子プロセスを生成するOSコスト」と、「別プログラムを実行するOSコスト」は、それぞれ分かれています。
ネットワークサービスの運用としては、大抵、1ポート1サービス(1機能)で運用 されているケースが多いと思います(あるポートで、telnet や ssh が 混在している事はあまり無いでしょう)。と言う事は、tnetsrv が動い ている時は、基本的にそのプログラムが変わる事は少ないとも言えます。
そこで、tnetsrv では、ダイナミックリンク機能を使用し、待ち受けを行う前に、 まず共有ライブラリをロードし、新規接続の際は、外部プログラムを exec する のではなく、すでにロードした共有ライブラリのある関数を呼び出す事で、exec にかかるコストを減らせるようにします。
inetd は、daytime (ホストの日付時刻を返す)サービスは、internal、すなわち 内部ですでに持っている状態です。
それとほぼ同じことが出来る共有ライブラリを、tnetio_daytime.so と言う名前で 作成してみます。
共有ライブラリ tnetio_daytime.so のソースは、以下の様になります。
/* tnetio_daytime.c - inetd の、daytime に相当するサービスライブラリ */ #include <stdio.h> #include <unistd.h> #include <time.h> #include <string.h> /* 本ライブラリがロードされた時に呼び出される */ void _init(void) { } /* 本ライブラリがアンロードされた時に呼び出される */ void _fini(void) { } /* tnetsrv は、この関数を呼び出す */ int tnetio_run(int in_fd,int out_fd,int argc,char **argv) { time_t tim = 0; char *tim_str = NULL; tim = time(NULL); /* 現在時間を取得 */ tim_str = ctime(&tim); /* 現在時間を文字列に変換 */ write(out_fd,tim_str,strlen(tim_str)); /* ネットワークに書き出す */ return 0; /* 終了する */ }
このソースをコンパイルします。
$ gcc -shared -fPIC -nostdlib -o tnetio_daytime.so tnetio_daytime.c
作成した tnetio_daytime.so を呼び出す様に、tnetsrv を起動します。ポート 10123 番で待ち受けます。
$ tnetsrv -s 10123 ./tnetio_daytime.so
別の端末から、telnet してみます。
$ telnet localhost 10123 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Thu May 15 14:40:12 2003 <---- ※ Connection closed by foreign host.
上記、※の部分が、共有ライブラリのtnetio_run()実行結果です。
tnetsrv は、共有ライブラリをロードする(-s オプション指定時)場合、関数名 tnetio_run() を探し、新規接続が行われた場合に、tnetio_run() を呼び出します。
tnetio_run() の関数仕様は、以下の通りです。
int tnetio_run(int in_fd,int out_fd,int argc,char **argv)
tnetio_run() 終了時、in_fd,out_fd が示すソケットは、必ずしもクローズする必要は ありません(tnetsrv が、tnetio_run() から帰ってきた後にクローズします)。
tnetio_run() の呼び出しタイミングは、原則的に外部プログラム実行タイミングと同じですが、以下の差異点があります。
共有ライブラリをロードするタイミングは、ソケットへの bind を行い、setuid()、setgid() した後に行われます。従って、共有ライブラリをロードすると、_init()が (存在していれば)呼び出されますが、それも、指定された uid/gid で呼び出されます。