Programming UNIX Sockets in C - Frequently Asked Questions: クライアントとサーバ(TCP/SOCK_STREAM)両方に関する質問 Previous Next Table of Contents
Andrew Gierth 氏 ( andreより:
私の知る限り…
相手側が (SO_LINGER
を使ったややこしいことをしないで) close()
するか終了したとすると、こちらの read()
の呼び 出しは 0 を返すはずです。同じ場合で、write()
呼び出しで何が 起こるかは、もうちょっとわかりづらいです。直後の呼び出し時ではな く、その次の呼び出し時にEPIPE
が返るでしょう。
もし相手が再起動するか l_onoff = 1, l_linger = 0
を設定して から閉じたとすると、read()
からは(最終的に) ECONNRESET
が返るか、write()
からは EPIPE
が返ることになるでしょ う。
さらに、write()
が EPIPE
を返すときは、同時に SIGPIPE
シグナルも発生することを指摘しておきます。すなわち、 このシグナルをハンドルするか無視しない限り、 EPIPE
エラーを 受け取ることは決してありません。
相手側に到達できないままになっている場合には、他のエラーが起こる でしょう。
write()
が 0 を返すことは論理的にはないと思います。 read()
は、相手側から FIN を受け取ったとき、そしてそれ以後 の呼び出しにおいては 0 を返すでしょう。
そうです、read()
が 0 を返すことに対応 しなければなりま せん。
例として、TCP 路からファイルを受け取っていると仮定しましょう。 read()
からの返却値はこのように取り扱ってください:
rc = read(sock,buf,sizeof(buf)); if (rc > 0) { write(file,buf,rc); /* ファイルに対するエラーチェックは省略 */ } else if (rc == 0) { close(file); close(sock); /* ファイルの取得に成功した */ } else /* rc < 0 */ { /* ファイルを閉じて削除する(完全なデータではないので) エラーを報告する、など */ }
man ページには "struct sockaddr *my_addr
" と示されています。 しかし sockaddr struct
は、実際に必要な構造体の単なるプレー スホルダーに過ぎず、どの種類のソケットであるかに応じて違った構造 体を渡さなくてはなりません。AF_INET
ソケットに対しては、 sockaddr_in 構造体が必要です。これには三つの重要なフィールドがあ ります。
- sin_family
これを AF_INET
に設定する。
- sin_port
ネットワークバイト順の 16 ビットのポート番号。
- sin_addr
ホストの IP アドレス。 これは struct in_addr
であり、それは s_addr
という一つのフィールドのみを 含み、それは u_long
です。
getservbyname()
関数を使ってください。これは servent
構造体へのポインタを返します。あなたの興味があるのは s_port
フィールドでしょう。これにはポート番号が、正しいバイト順序で入っ ています(つまり htons()
を呼び出す必要はない)。サンプルルー チンを以下に示します。
/* サービス名とサービス種別と取って、ポート番号を返す。もしサー ビス名が見つからなければ、それを十進数として使おうとする。ポー ト番号はネットワーク用のバイト順序で返される。*/ int atoport(char *service, char *proto) { int port; long int lport; struct servent *serv; char *errpos; /* 最初に /etc/services から読もうとする */ serv = getservbyname(service, proto); if (serv != NULL) port = serv->s_port; else { /* services にはなかった。 数字なのかな? */ lport = strtol(service,&errpos,0); if ( (errpos[0] != 0) || (lport < 1) || (lport > 5000) ) return -1; /* 不正なポート番号 */ port = htons(lport); } return port; }
もし終了しようとしているのであれば、全ての UNIX では開いたファイ ルディスクリプタを終了時に閉じてくれる、ということを Andrew が保 証してくれました。終了するのでなければ、通常のclose()
呼び 出しによって閉じることができます。
この質問はよく、close()
しようとしている人達から尋ねられます。 なぜなら、その人達はそれがするべきことだと思っていて、そして netstat を実行してそのソケットがまだ生きていることを見つけるから です。そう、close()
は正しい方法です。TIME_WAIT 状態につい て、そしてなぜそれが重要であるかを読みたければ、 2.7 TIME_WAIT 状態について説明してください。 を参照して ください。
Michael Hunter ( mphunte氏より:
shutdown()
は、TCP を使ってサーバへ要求を送ることをいつ終了 したのか、の線引きをするのに便利です。典型的な使い方は、サーバに 要求を送り、続けて shutdown()
を行なうことです。サーバはあ なたの要求を受け取り、続けて EOF
(ほとんどの UNIX の実装で は 0 バイトの read)を受け取るでしょう。これは、それであなたの要 求が全てである、ということをサーバに伝えます。そしてあなたはその ソケットの読み出しでブロックします。サーバはあなたの要求を処理し、 必要なデータをあなたに送り返し、続けて close します。あなたがそ の要求に対する応答を全て読み出した後は、あなたは全ての応答を受け 取ったということを示す EOF
を読み出すことになるでしょう。 TTCP (TCP for Transactions -- R.Stevens 氏のホームページを参照) は TCP トランザクションの管理のより良い方法を提供しているという ことは憶えておくべきです。
S.Degtyarev ( de氏はこれについて、とても良い徹底的 なメッセージを書いてきてくれました。彼は、一方が「読み出し」プロ セス、もう一方が「書き込み」プロセスであるときのクライアントプロ セスの同期を手助けする shutdown() の使い方の実用的な例を示してく れました。彼のメッセージの一部を以下に示します。
ソケットは、データ転送とクライアント、サーバ間のトランザクション に使われるという点でパイプと非常に似ていますが、双方向であるとこ ろがパイプと異なっています。ソケットを使うプログラムはよく fork()
し、各プロセスはソケットディスクリプタを継承します。 パイプベースのプログラムでは、データの喪失とデッドロックを避ける ために、パイプの使用されていない側の終端を全て閉じて、そのパイプ ラインを一方向にすることが強く推奨されています。ソケットでは、一 方のプロセスに送信だけを許し他方を受信だけを許す、という方法はな いので、常に順序関係を心に留めておく必要があります。
一般的に close()
と shutdown()
との違いは以下のような 点です。close()
はそのプロセスのソケット ID は閉じますが、 他方のプロセスがそのソケット ID を共有しているならば、このコネク ションは開いたままです。このコネクションは読み出し、書き込みの両 方に対して開いたままであり、そしてこれが非常に重要になる時がある のです。shutdown()
はそのソケット ID を共有している全てのプ ロセスのコネクションを破棄します。read しようとしたものは EOF
を検出し、write しようとしたものは SIGPIPE
を受け 取るでしょう。これはおそらくカーネルのソケットバッファが一杯になっ てから遅れて発生します。それに加えて、shutdown()
にはどのよ うにコネクションを閉じるかを示す二番目の引数があります: 0 はそれ 以降の読み出しを無効にする意味であり、1 は書き込みを無効にし、2 は両方を無効にします。
以下の簡単な例は、非常に単純なクライアントプロセスの一部です。こ れはサーバとのコネクションを確立した後に fork します。そして子は EOF
を受け取るまでキーボード入力をサーバに送り、親はサーバ からの返事を受け取ります。
/* * サンプルクライアントの断片 * 変数宣言とエラー処理は省略 */ s=connect(...); if( fork() ){ /* 子は、標準入力を ソケットにコピーする */ while( gets(buffer) >0) write(s,buf,strlen(buffer)); close(s); exit(0); } else { /* 親は、返事を受け取る */ while( (l=read(s,buffer,sizeof(buffer)){ do_something(l,buffer); /* サーバからのコネクション切断を期待している */ /* 注意: ここでデッドロックする */ wait(0); /* 子の終了を待つ */ exit(0); }
これは何を期待しているのでしょうか? 子は標準入力から EOF
を 検出し、そのソケットを close し(コネクションが破棄されると仮定す る)、そして終了します。一方サーバは EOF
を検出し、コネクショ ンを closeして終了します。しかしその代わりに何を見ることになるで しょうか? 親プロセスのソケットインスタンスは、その親が書き込むこ とは無いにも関わらず、書き込み、読み出しに対して開いたままになっ てしまいます。サーバは EOF を検出することは決して無く、クライア ントからのさらなるデータを永遠に待ち続け、サーバもハングします。 なんと予想外のデッドロック! (まあ、どんなデッドロックも予想外の ものだけど :-)
このクライアントの一部は以下のように変更すべきです。