Previous Next Table of Contents

3. クライアントアプリケーションの作成 (TCP/SOCK_STREAM)

3.1 文字列をインターネットアドレスに変換するにはどうすれば良いのですか?

もしホストのアドレスをコマンド行から読んでいるのであれば、それが aaa.bbb.ccc.ddd スタイルのアドレスであるか host.domain.com スタ イルのアドレスであるかを知ることはできないでしょう。私がこれに関 して行う方法は、まず aaa.bbb.ccc.ddd タイプのアドレスであるとし てそれを使おうとします。そして、それが失敗したら、名前の検索を行 います。以下に例を示します:

/* アスキー文字列を in_addr 構造体に変換する。アドレスがみつから
   なかった場合には NULL が返される。 */
struct in_addr *atoaddr(char *address) {
  struct hostent *host;
  static struct in_addr saddr;

  /* まず aaa.bbb.ccc.ddd 形式であるとして試してみる。 */
  saddr.s_addr = inet_addr(address);
  if (saddr.s_addr != -1) {
    return &saddr;
  }
  host = gethostbyname(address);
  if (host != NULL) {
    return (struct in_addr *) *host->h_addr_list;
  }
  return NULL;
}

3.2 私の作ったクライアントを、ファイヤーウォール/プロキシーサーバを越えて動作させるにはどうすればよいでしょうか?

もし各サービス用に独立したプロキシーを通しているのであれば、何も する必要はないはずです。sockd を通して使っているのであれば、あな たのアプリケーションを「socks 化」する必要があるでしょう。これを 行う方法の詳細は socks パッケージ自身に入っています。これは以下 の場所にあります:

ftp://ftp.net.com/socks.cstc/socks.cstc.4.2.tar.gz

socks の FAQ は以下で取得できます:

ftp://coast.cs.purdue.edu/pub/tools/unix/socks/FAQ

3.3 なぜ、サーバが accept() する前に connect() が成功するのですか?

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

ソケットに対して listen() 呼び出しを行った後は、カーネルは それに対してコネクションを accept する準備を行います。普通の UNIX の実装では、正当な SYN セグメント(コネクション要求)が到着す ると、SYN ハンドシェイクを 即座に 完了させ、新しいコネクショ ンのためのソケットを作成し、その新しいソケットを accept() 呼び出しですぐ利用できるように内部キューに保持しておく、というよ うに動作します。ですから、ソケットは accept が行われる 前に 完全にオープンするわけです。

これに関するその他の要素は、listen() の「バックログ」パラメー タです。これは、その完了したコネクションが同時にいくつまでキュー に入れられるか、を定義します。もしその指定した数を越えてしまった ら、新しく到着したコネクションは単純に無視されます(そしてリトラ イを起こさせます)。

3.4 どうして、一つ以上のサーバを使っているときにサーバのアドレスを失ってしまうことがあるのでしょうか?

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

hostent 構造体をじっくりと眺めて見てください。その中のほとんど全 てがポインタであることに気がつかない? これらのポインタの 全 て は静的に割り当てられたデータとして参照されます。

例えば、こんな風にしたとすると:

    struct hostent *host = gethostbyname(hostname);

すると(もうお分かりのように)、次に呼び出される gethostbyname() が 'host' によって指し示されている構造体を 上書きしてしまうでしょう。

ですが、このようにして:

    struct hostent myhost;
    struct hostent *hostptr = gethostbyname(hostname);
    if (hostptr) myhost = *host;

それが上書きされる前に hostent のコピーを作っても、やは りそれでも 次に呼び出される gethostbyname() によってぶち壊 されてしまいます。なぜなら、 myhost は上書きされないけれど も、それが指し示しているデータが全て上書きされてしまうからです。

これは hostent 構造体の正しい「深層コピー」を行うことによっ ても回避できますが、これは退屈な仕事です。私の推奨する方法は、 hostent の必要なフィールドだけを、独自の方法で格納すること です。

Robin Paterson 氏 ( etmrpat@etm.ericsson.se) による追加:

マルチスレッドプログラミングに対応している MT safe なライブラリ について触れておいても良いかもしれません。私が今書いている Solaris のマシンでは、gethostbynamegethostbyname_r (_r は再入可 reentrant のこと)というものがあります。主要な 違いは、hostent 構造体の格納場所を あなたが 提供するという ことで、静的領域への単なるポインタではなく、常にローカルなコピー を持つことになります。

3.5 connect() システムコールにタイムアウトを設定するにはどうすればよいのでしょうか?

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

通常はこれを変更することはできません。Solaris では ndd tcp_ip_abort_cinterval パラメータによって、カーネル毎にこれ を行うことはできます。

connect の時間を短くする最も簡単な方法は、connect() の呼び 出しの近くに alarm() を置くことです。より難しい方法は、ソケッ トを非ブロックに設定した後 select() を使うという方法です。 また、コネクト時間は短くすることだけができるのであって、普通は長 くする方法はない、ということにも気をつけてください。

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

まず、ソケットを作成してそれを非ブロックモードにし、そして connect() を呼び出します。三つの可能性があります:

もしコネクションが成功すれば:

もしコネクションが失敗すれば:

3.6 ポート番号は、私の書いているクライアントプログラムにおいて bind() すべきでしょうか? あるいは connect() 呼び出しによってシステムに選ばせるべきでしょうか?

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

** システムにあなたのクライアントのポート番号を選ばせてくだ さい **

例外は、どのクライアントのポートからのコネクションを許すかについ て、サーバが選り好みするように書いてある場合です。rlogind と rshd は古典的な例です。これは通常、 UNIX 特有の(かなり貧弱な)認 証機構の一部です。その意図は、サーバは root 権限を持つプロセスか らのみのコネクションを許すということです(この機構が脆弱なのは、 多くの OS (例えば MS-DOS) では誰でも好きなポートを bind できるか らです)。

rresvport() ルーチンは、この機構を使っているクライアントを 手助けするために存在しています。これは基本的には socket() + bind() でポート番号を 512...1023 の範囲から選ぶ、ということ と同一です。

サーバが クライアントの ポート番号についてうるさく言わない のであれば、クライアントにおいてあなたが自分で割り当てようとはし ないでください。単に connect() に選ばせてください。

もしクライアントにおいて、正常に動作するまでとして、固定ポート番 号から開始する単純な機構を使って一貫した値で bind() を呼び出して いるとすると、あなたはたくさんのトラブルの中に身を投じることにな ります:

問題は、あなたのコネクションのサーバ側がアクティブクローズを行っ た場合(例えば、クライアントが「終了」コマンドをサーバに送り、サー バはそのコネクションを閉じて応答する場合)です。これはコネクショ ンのクライアント側を CLOSED 状態にして、サーバ側を TIME_WAIT 状 態にします。ですから、クライアントが終了した後、クライアント側で はそのコネクションの形跡は残りません。

ここで、そのクライアントをもう一度実行します。するとそれは同じポー ト番号を選ぶでしょう。なぜならそのクライアントから見た限りでは、 そのポートは空いているからです。しかし connect() を呼び出す と、サーバはあなたがすでに存在するコネクションに(TIME_WAIT 状態 にはあるけれども)重複しようとしていると思い込みます。これは、こ の接続を拒否するための全く正当な権利があるので、あなたは connect()から ECONNREFUSED を受け取るんじゃないかと私 は推測します(システムによっては、どんな場合でもコネクションを許 すときもあるかもしれませんが、しかしそれをアテにしては いけ ません)。

この問題が 特に 危険なのは、これがクライアントとサーバが 異なった マシン上で実行されるまで現れないからです(もし同一 のマシン上にあったとすると、クライアントは前と同じポート番号は 選びません)。それによって、(もしあなたが、多くの人がやって るんじゃないかと思われる方法のように、最初はクライアントとサーバ を同一のマシン上で試験を行っているのだとすると)あなたは開発サイ クルにさらに深く食い込むことになるかもしれません。

たとえあなたのプロトコルがクライアントからクローズする場合であっ ても、それでもまだこの問題を引き起こす方法はあります(例えば、サー バの停止させる)。

3.7 どうして、サーバが動いてないときは「コネクション拒否 "connection refused"」されてしまうのですか?

connect() 呼び出しは、コネクションの確立を待っているときの みブロックします。相手側に待っているサーバがいないときは、コネク ションが確立できないことが通知され、あなたが見たエラーメッセージ を返してあきらめるのです。これは、クライアントが存在しないサービ スを永遠に永久に待ってしまうという場合が有り得ないので、良いこと なのです。ユーザは単にコネクションが確立されるのを待っているだけ だと思うでしょうし、そしてあきらめた後に、ぶつぶつと小声で、安っ ぽいソフトウェアだと文句を言うのです。

3.8 どれくらいの量の情報がソケットを通して入ってくるのか分からないときはどうすればよいのですか? 動的なバッファを持つ方法はあるのですか?

この質問は Niranjan Perera 氏 ( perera@mindspring.com) から尋ねられました。

入ってくるデータの大きさが分からないときは、可能な限り大きな(お るいは適当な)大きさのバッファを作ることも、また読み込み中に動的 にバッファをリサイズすることもできます。大きなバッファを malloc() した時は、(全て、でなければ)ほとんどの UNIX の種類 においては、アドレス空間だけを割り当てて、物理的なメモリのページ は割り当てられません。バッファがだんだん使われてくるにつれて、カー ネルは物理メモリを割り当てます。これの意味するところは、大きなバッ ファを malloc することはそのメモリを使うまで資源を無駄にすること はないので、数キロしか予想されていなくても1メガのメモリを要求す ることは十分許容できる、ということです。

一方、カーネルの内部動作に依存しない、よりエレガントな解決法は、 realloc() を使って、バッファを必要に応じて例えば 4 キロの塊(4 キ ロは多くのシステムでのメモリのページサイズ) で拡大するという方法 です。いつの日かこれを sockhelp.c の例に追加するかもしれません。


Previous Next Table of Contents