Previous Next Table of Contents

4. サーバアプリケーションの作成 (TCP/SOCK_STREAM)

4.1 bind() から「アドレス使用中 "address already in use"」が返されるのはどうしてですか?

それは、そのアドレスが使用中だからです(え? それはもうよく分かっ てるって?)。これが起こる最もありがちな理由は、サーバをストップし て、その直後に再起動したときです。最初のサーバの実体によって使わ れていたソケットがまだ有効なのです。これについては、 2.7 TIME_WAIT 状態について説明してください 2.5 ソケットを正しく閉じるにはどうす ればよいのですか? でさらに説明されています。

4.2 どうしてソケットがクローズしてくれないのでしょうか?

あなたが close() システムコールを発行したときは、それはソケッ トへのインターフェースをクローズしているということで、ソケット自 身をクローズしているのではありません。ソケットをクローズするのは カーネルに任されています。ときによって、本当に技術的な理由によっ て、ソケットはあなたがクローズした後数分間生き続けるときがありま す。例として、サーバ側で、数分間、 TIME_WAIT 状態になるソケット というのは普通です。この範囲は 20 秒から 4 分と報告されています。 公式は標準ではこれは 4 分とすべきである、としています。私の手元 の Linux システムでは約 2 分です。これについては 2.7 TIME_WAIT 状態について説明してください で非常に詳細に説明されています。

4.3 どうすれば私の書いたサーバをデーモンにできますか?

ここで二つの方法を取ることができます。一つ目は inetd を使って面 倒な仕事は全部やってもらうことです。二つ目はその面倒な仕事を全部 自分自身でやる方法です。

inetd を使うのなら、単純に stdinstdoutstderr をソケットとして使うだけです(これら三つは全て、実際のソケットか ら dup() によって作成されます)。あなたはこれらをソケットの ように使うことができます。あなたの仕事が終わったら、inetd プロセ スはソケットのクローズさえもやってくれます。

もしあなたが自分で独自のサーバを書きたいのであれば、Richard Stevens 氏による "Unix Network Programming" ( 1.6 [ある本の題名] という本のソースコードはどこ から取得できますか? 参照)に詳しい説明があります。また、 comp.unix.programmer に投稿された、Nikhil Nair 氏( nn201@cus.cam.ac.uk) の コードも取り上げます。SIGPIPE に対処をしないとアプリケーションが 終了してしまうので、SIGPIPE を無視するコードを追加しても良いかも しれません( ingo@milan2.snafu.de 氏のご指摘に感謝します)。

これは GNU C ライブラリマニュアル(オンラインドキュメント)から作
り上げました。これが私の書いたコードです。必要に応じて修正してく
ださい:


#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>

/* グローバル変数 */
...
volatile sig_atomic_t keep_going = 1; /* プログラムの終了を制御する */


/* 関数プロトタイプ宣言: */
...
void termination_handler (int signum); /* 終了前の後始末 */


int
main (void)
{
  ...

  if (chdir (HOME_DIR))         /* データファイルのあるディレク
                                   トリに移動する */
   {
     fprintf (stderr, "`%s': ", HOME_DIR);
     perror (NULL);
     exit (1);
   }

   /* Become a daemon: */
   switch (fork ())
     {
     case -1:                    /* fork できない */
       perror ("fork()");
       exit (3);
     case 0:                     /* 子、プロセスはデーモンになる: */
       close (STDIN_FILENO);
       close (STDOUT_FILENO);
       close (STDERR_FILENO);
       if (setsid () == -1)      /* 新規セッションの要求 (ジョブ制御) */
         {
           exit (4);
         }
       break;
     default:                    /* 親は呼び出しプロセスに戻る: */
       return 0;
     }

   /* 終了前に後始末をするシグナルハンドラを設置する: */
   if (signal (SIGTERM, termination_handler) == SIG_IGN)
     signal (SIGTERM, SIG_IGN);
   signal (SIGINT, SIG_IGN);
   signal (SIGHUP, SIG_IGN);

   /* プログラムのメインループ */
   while (keep_going)
     {
       ...
     }
   return 0;
}

void
termination_handler (int signum)
{
  keep_going = 0;
  signal (signum, termination_handler);
}

4.4 同時に一つよりも多くのポートで listen するにはどうすればよいのですか?

最も良い方法は select() 呼び出しによる方法です。これは、カー ネルに、ソケットが利用できるようになったことを知らせてくれるよう にします。この呼び出しで、複数のソケットと入出力を行う一つのプロ セスを作ることができます。もしソケット 4、6、10 のソケットにおい て接続を待ちたければ、以下のコードの断片を実行することでできます:

fd_set socklist;

FD_ZERO(&socklist); /* 最初に常に構造体をクリアする */
FD_SET(4, &socklist);
FD_SET(6, &socklist);
FD_SET(10, &socklist);
if (select(11, NULL, &socklist, NULL, NULL) < 0)
  perror("select");

11 (select() への最初のパラメータ) よりも小さなファイルディ スクリプタと socklist のメンバーのうちのどれかが書き込み可 能となった時点で、カーネルが通知してくれます。詳細は select() の man ページを参照してください。

4.5 SO_REUSEADDR は正確には何をするのですか?

このソケットオプションは、たとえそのポートが使用中(TIME_WAIT 状 態)であっても、とにかく再利用してしまうようにカーネルに指示しま す。他の状態で使用中のときには、やはりアドレス使用中のエラーが返 ります。このオプションは、サーバがシャットダウンされて、そしてそ の直後、ソケットがまだそのポートにおいてアクティブである間に再起 動されたときに便利です。ただ予想外のデータが何か入ってきたときに、 サーバを混乱させるかもしれないということは知っておいてください。 でも、これは起こる可能性はありますが、まず起こらないです。

「ソケットとは五つ組(プロトコル、ローカルアドレス、ローカルポー ト、リモートアドレス、リモートポート)である。SO_REUSEADDR は単に ローカルアドレスが再利用できるというだけのことである。この五つ組 はやはり唯一のものに違いないのだ!」と Michael Hunter 氏 (mphunter@qnx.com) によって指摘されています。これは正しいです。 そしてこれが予想外のデータがサーバに届くことがほとんど有り得ない ことの理由です。危険な点は、そのような五つ組がまだネットワーク上 に漂っていて、それがあちこち動いている間、同じシステム上の同じク ライアントからの新しいコネクションが、たまたま同じリモートポート を使ってしまった場合です。これについては 2.7 TIME_WAIT 状態について説明してください で Richard Stevens 氏によって説明されています。

4.6 SO_LINGER は正確には何をするのですか?

ある種の UNIX では何もしません。他のものにおいては、TCP コネクショ ンを正しくクローズする代わりに中断するようカーネルに指図します。 これは危険なことがあります。これについてよくわからなければ、 2.7 TIME_WAIT 状態について説明してください を参照してください。

4.7 SO_KEEPALIVE は正確には何をするのですか?

Andrew Gierth 氏 ( andrew@erlenstar.demon.co.uk) より:

SO_KEEPALIVE オプションは、何のデータも送受信されずに長時間 (デフォルトでは 2 時間以上)経ってしまった時に、あるパケット (「keepalive probe」と呼ばれる) をリモートシステムに送るいうこと を行います。このパケットは相手側からの ACK 応答を誘発させるよう に意図されています。これによって到達不可能になってしまった通信相 手(例えば電源が切れた、ネットワークから切断されてしまった)の検出 を可能にします。さらなる議論は 2.8 相手側が死んだことを検出するのにどうしてこんなに時間が かかるのですか? を参照してください。

2 時間という数字は、RFC1122 の "Requirements for Internet Hosts" から来ているということに注意してください。このきっちりした値は変 更可能であるべきなのですが、私はそれが困難であると感じることがよ くあります。コネクション毎に keepalive 期間を設定できることがで きる実装は、私の知っているものでは SVR4.2 のみです。

4.8 1024 より小さい番号のポートに bind() するにはどうすればよいのですか?

Andrew Gierth 氏 ( andrew@erlenstar.demon.co.uk) より:

1024 より小さいポート番号へのアクセス制限は、特に UNIX での(本当 に貧弱な)セキュリティ機構の一部です。この意図は、サーバ(例えば rlogind、rshd)がクライアントのポート番号をチェックして、そしてそ れが 1024 より小さければ、その要求がクライアント側で正しく認証さ れていると仮定することができる、ということです。

実行上の結論としては、1024 より小さいポート番号への bind は、実 効 UID == root であるプロセスのために予約されている、ということ になります。

これは時折、それ自身でセキュリティ問題を引き起こすことがあります。 例えば、サーバプロセスが well-known ポートに bind する必要がある のに、そのサーバ自身は root 権アクセスを必要と しない とき です(例えばニュースサーバ)。これはよく、ソケットを bind だけして、 そして実ユーザID を元に戻して本当のサーバを exec() するとい う、小さなプログラムを書くことによって解決されます。このプログラ ムは root に setuid させることができます。

4.9 クライアントのアドレス・ホスト名をサーバ側が知るにはどうすればよいですか?

Andrew Gierth 氏 ( andrew@erlenstar.demon.co.uk) より:

コネクションを accept() した後、getpeername() を使って クライアントのアドレスを取得してください。もちろんクライアントの アドレスは accept() によっても返されますが、これを使うには、 accept の前にアドレス長のパラメータを初期化しておくことが重要で す。

Jari Kokko 氏( jkokko@cc.hut.fi) は、クライアントアドレスを決定する以下 のコードを提供してくれました:

int t;
int len;
struct sockaddr_in sin;
struct hostent *host;

len = sizeof sin;
if (getpeername(t, (struct sockaddr *) &sin, &len) < 0)
        perror("getpeername");
else {
        if ((host = gethostbyaddr((char *) &sin.sin_addr,
                                  sizeof sin.sin_addr,
                                  AF_INET)) == NULL)
            perror("gethostbyaddr");
        else printf("remote host is '%s'\n", host->h_name);
}

4.10 私が書いているサーバのためのポート番号は、どうやって選ぶべきなのでしょうか?

割り当てられている登録ポートのリストは、STD 2 または RFC 1700 で 知ることができます。まだ登録されていなくて、あなたのシステムの /etc/services に入ってないものを選んでください。また、その他のサー ビスで使っている未登録ポート番号と衝突したときのために、ポート番 号をユーザがカスタマイズできるようにしておくことも良い方法です。 これを行う最も良い方法は、サービス名をハードコーディングしておい て、実際のポート番号は getservbyname() を使って検索するとい う方法です。この方法によって、ユーザは単に /etc/services ファイ ルを修正することによって、あなたのサーバが bind するポートを変更 できるようになります。

4.11 SO_REUSEADDR と SO_REUSEPORT との違いは何ですか?

SO_REUSEADDR は、サーバに TIME_WAIT 状態のアドレスに bind することを許します。これは二つ以上のサーバが同じアドレスに bind することは許しません。このフラグの使用は、セキュリティ上の危険性 を生み出すことがあると言われています。それは別のサーバが、 (INADDR_ANY とは対照的に)特定のアドレスに bind することによっ て、同じポートに bind することができてしまうからです。 SO_REUSEPORT フラグは、全てのプロセスで SO_REUSEPORT オプションを使うと決めておいた同じアドレスに対して、複数のプロセ スから bind することを許します。

Richard Stevens 氏( rstevens@noao.edu)より:

これは 4.4BSD のマルチキャストのコードで登場した新しいフラグです (とはいえ、そのコードはどこか他の所から来たものなんだけど。だか ら誰がこの新しい SO_REUSEPORT フラグを発明したのかはよくわ かりません)。

このフラグでできることは、そのポートのユーザが全員この フラグを指定したときに限り、使用中のポートを再 bind できるという ことです。これの目的はマルチキャストアプリケーションのためなのだ と思います。それは一つのホスト上で同じアプリケーションを実行して いるとすると、その全てが同じポートを bind する必要があるからです。 でも他の使い方もあるかも知れません。以下の例は二月に投稿された記 事からです:

Stu Friedberg 氏 ( stuartf@sequent.com) より:

SO_REUSEPORT はftpd のデータコネクションの確立手順における 「10回 bind しようとする」ハックを除去するのにも便利です。 SO_REUSEPORT なしでは、クライアントへの逆コネクションの準備 において、ただ一つの ftpd スレッドのみしか TCP (lhost, lport, INADDR_ANY, 0) に bind できません。負荷が高い状況においては、 「10回 bind」ハックが適応できるよりも多くのスレッド衝突がおこり ます。SO_REUSEPORT ありだと全てうまく動作し、このハックは不 必要になります。

また、DEC OSF はこのフラグをサポートしていると聞いています。さら に、4.4BSD ではマルチキャストアドレスに bind しているのなら、 SO_REUSEADDRSO_REUSEPORT と同じであると考えられま す("TCP/IP Illustrated, Volume 2" の p.731)。Solaris 上では、 SO_REUSEPORTSO_REUSEADDR で単に置き換えればよいと 思います。

Stevens 氏の後の投稿より、多少編集しています:

基本的には SO_REUSEPORT はマルチキャストが追加されたときに 出てきた BSD 由来のものです。とはいえ Steve Deering 氏のオリジナ ルのコードでは使われていませんでしたが。BSD 派生のシステムの中に はこれを含んでいるものもあると思います(OSF、今は Digitanl UNIX かな?)。SO_REUSEPORT は、bind する人が全てこれを指定したと きに限って、同じアドレス *と* ポートにバインドすることができるよ うにします。しかし、マルチキャストアドレスを bind しているとき (そのメインの使い方)は、SO_REUSEADDRSO_REUSEPORT と同一のものと考えられます(p. 731, "TCP/IP Illustrated, Volume 2")。ですからマルチキャストアプリケーションの移植性のためには、 わたしはいつも SO_REUSEADDR を使います。

4.12 マルチホームのサーバを書くにはどうすればよいですか?

元の質問は実際は Shankar Ramamoorthy 氏 ( shankar@viman.com) によるものです:

あるサーバをマルチホームのホスト上で実行したいと思っています。そ のホストは二つのネットワークに属していて、二つのイーサネットカー ドを持っています。このマシン上で、あらかじめ決まっているポート番 号に bind してサーバを走らせたいのです。そしてどちらのサブネット 上のクライアントからでも、そのポートにブロードキャストパケットを 送ることができるようにして、それをサーバに受け取らせたいのです。

そして Andrew Gierth 氏 ( andrew@erlenstar.demon.co.uk) による回答は:

このシナリオにおける最初の疑問点は、どちらのサブネットからパケッ トが来たかを知る必要がありますか? ということです。これがどんな場 合でも確実に決定することができるのか、私には全くわかりません。

もしどちらでも構わないのであれば、必要なものは INADDR_ANY に bind された一個のソケットだけです。これで物事はとっても単純に なります。

もしどちらなのか 構う のであれば、複数のソケットを bind し なければなりません。あなたの投稿されたコードの中では明らかにそう しようとしていたので、たぶんそうなのだと想定します。

以下のようなもので動作するだろうと期待していたんですが、これは動 きますか? Sparc 上で Solaris 2.4/2.5 を使っています。

私は Solaris にアクセスできないのですが、その他の UNIX での私の 経験を基にコメントします。

[Shankar 氏のオリジナルのコードは省略]

あなたがやっていることは、hosts/NIS/DNS 上に登録されている現在の ホストのユニキャストアドレス全てを bind しようとしている、という ことです。これは実際のものを反映しているかも知れないし、反映して いないかもしれません。しかしもっと重要なことは、これはブロードキャ ストアドレスを無視するということです。ユニキャストアドレスに bind されたソケットは、ブロードキャストアドレスの行き先を持った 入力パケットを 見ない というのが大多数の実装であると思われ ます。

私の取った方法は、 SIOCGIFCONF を使ってアクティブなネットワー クインターフェースのリストを取得し、SIOCGIFFLAGSSIOCGIFBRDADDR を使ってブロードキャスト可能なインタフェース を特定し、ブロードキャストアドレスを得る、というものです。そして 各ユニキャストアドレス、各ブロードキャストアドレス、そして INADDR_ANY にも bind します。この最後のものは宛先に INADDR_BROADCAST が入って電送されているパケットを捕まえるの に必要です(SO_REUSEADDR は、アドレスを指定したときと同様に、 INADDR_ANY を bind するために必要です)。

これで私の欲しいものに近づきました。助言を:

4.13 一回に一文字だけ読み出すにはどうすればよいのですか?

この質問は、サーバの試験に telnet を使って、一回に一文字のキー入 力を処理したい、という人々から聞かれます。正しいテクニックは、仮 想端末(pty)を使うことです。これについては長くなります。

Roger Espel Llima 氏( espel@drakkar.ens.fr) によれば、サーバに次の制御文字の列 を送信させることもできます: 0xff 0xfb 0x01 0xff 0xfb 0x03 0xff 0xfd 0x0f3 これは IAC WILL ECHO IAC WILL SUPPRESS-GO-AHEAD IAC DO SUPPRESS-GO-AHEAD と翻訳されます。 これがどういう意味なのかについてのさらなる情報は、std8、std28、 std29 を見てください。Roger 氏はまた、以下のような助言を与えてい ます:

pty の使用は、子プロセスを実行してその入出力をソケットに渡す場合 においても正しい方法です。

pty 関連は、 FAQ に追加したいソース例のリストに追加しておきます。 どなたかpty の使い方を説明してくれるソースを持っていてこの FAQに (著作権なしで)寄付していただけるなら、どうか私にメールください。

4.14 サーバからあるプログラムを exec() してそれにソケットの入出力を繋げようとしているんですが、データが全部渡っていかないんです。どうしてでしょう?

もしあなたが実行しようとしているプログラムが printf() など (stdio.h でのストリーム)を使っているのなら、あなたは二つの バッファを扱わなくてはなりません。カーネルは全てのソケット入出力 をバッファリングします。これは section 2.11 で説明されています。二つ目のバッファがあなたを不幸にして いる原因です。これは stdio バッファであって、この問題は Andrew によってよく説明されています:

(この質問に対して手っ取り早く答えると、socket よりも pty を使う べきです。この記事の残りの部分ではそれはなぜかを説明します。)

まず第一に、setsockopt() で制御されるソケットバッファは、 stdio のバッファリングとは 一切関係がありません。これを 1に 設定するのは「大外し Wrong Thing(tm)」であることが保証されてい ます。

以下の図が少し分かりやすくしてくれるでしょう:

        Process A                   Process B
    +---------------------+     +---------------------+
    |                     |     |                     |
    |    mainline code    |     |    mainline code    |
    |         |           |     |         ^           |
    |         v           |     |         |           |
    |      fputc()        |     |      fgetc()        |
    |         |           |     |         ^           |
    |         v           |     |         |           |
    |    +-----------+    |     |    +-----------+    |
    |    | stdio     |    |     |    | stdio     |    |
    |    | buffer    |    |     |    | buffer    |    |
    |    +-----------+    |     |    +-----------+    |
    |         |           |     |         ^           |
    |         |           |     |         |           |
    |      write()        |     |       read()        |
    |         |           |     |         |           |
    +-------- | ----------+     +-------- | ----------+
              |                           |                  User space
  ------------|-------------------------- | ---------------------------
              |                           |                Kernel space
              v                           |
         +-----------+               +-----------+
         | socket    |               | socket    |
         | buffer    |               | buffer    |
         +-----------+               +-----------+
              |                           ^
              v                           |
      (AF- and protocol-          (AF- and protocol-
       dependent code)             dependent code)

これら二つのプロセスがお互いに通信してると思ってください(実際の 通信メカニズムは、あまり関係がないのであえて省略しています)。プ ロセス A によって stdio バッファに書き込まれたデータは、プロセス B からは全くアクセスできないということがわかるでしょう。そのバッ ファからカーネルに(write()によって)フラッシュさせること が決まった時にのみ、実際にデータが他方のプロセスに配送させられる のです。

プロセス A におけるバッファリングに影響を与えるために保証されて いる方法は、コードを変更することだけです。しかし、stdout に対す るバッファリングはデフォルトでは、その内部のファイル記述子が端末 を指しているかどうかによって制御されます。つまり一般には、端末へ の出力は行単位のバッファリングであり、非端末(ファイル、パイプ、 ソケット、tty 以外のデバイスなどが含まれるがそれだけに留まらない) への出力は完全にバッファリングされます。ですからお望みの動作は、 通常 pty デバイスを使用することで得られます。これが、例えば 'expect' プログラムが行なっていることです。

stdio バッファ(と FILE 構造体と、その他 stdio に関するもの 全て)はユーザレベルのデータであるので、exec() 呼び出しを越 えて保存されることはありません。このため、exec する前に setvbuf() を使おうとしても効果がありません。

これに代わる解決法が二つほど Roger Espel Llima 氏( espel@drakkar.ens.fr) によって提案されています:

もしそれが可能であればですが、pty の内部で実行してその入出力をバッ ファリングするだけの単体のプログラムを使う方法もあります。これを 行なpty.tar.gz という名前のパッケージを見たことがあります。 archie か AltaVista あたりで探してみてください。

また別の方法として(**警告、邪悪なハックです**)、それを サポートしているシステム上であればですが(SunOS、Solaris、Linux ELF はサポートしています。他については分かりません)、メインプロ グラムにおいて LD_PRELOAD に共有実行形式(*.so) の名前を putenv() して、そしてその .so 内で、あなたが exec しようと しているプログラムで使われていることが前もって分かっている libc の共通関数を再定義してしまうのです。これで実行しているプログラム の「制御を得る」事ができます。最初に実行された時にそのプログラム の代理として setbuf(stdout, NULL) を実行し、そして dlopen() + dlsym() を使ってオリジナルの libc 関数を呼 び出します。そして dlsym() の値を static 変数に取っておいて、 それ以降は単にそれを呼び出すだけです。

(編集者よりの注: 私は未だに pty の使い方の例を書き終えてませ んが、非ブロックのプログラム例を終えた後にできるだろうと思ってい ます。)


Previous Next Table of Contents