Previous Next Table of Contents

5. UDP/SOCK_DGRAM アプリケーションの作成

5.1 どんな時に TCP ではなく UDP を使うべきなのでしょうか?

UDP は、あるシステムから別のシステムにメッセージを送るのに、順序 は重要ではなく、全てのメッセージが他方のマシンに届く必要がないと いう時には有効です。ですから、私は UDP を、この FAQ のコード例を 書く時にしか使ったことがないのです。普通は TCP がより良い解決法 です。メッセージが望みの目的地に届くことの保証や、メッセージ順序 を保証させるためのコードを書かなくても済みます。あるプロジェクト にあなたが追加したコードというものには全て、非常に高価なバグが潜 んでいる可能性があるということを憶えておきましょう。

もし TCP があなたの要求に対してあまりにも遅すぎるのであれば、メッ セージ順序と信頼性を犠牲にしても構わないという限り、UDP によって もっと良いパフォーマンスが得られるかもしれません。

Philippe Jounin 氏が追加します...

5.1 章では、UDP は TCP よりも良いスループットが得られるとありま す。これはいくつかのルータを越えなければならないのであれば、そん なことは滅多にありません。

例えば、二つの LAN が X.25 を通して接続されているのであれば(ヨー ロッパでは一般的な方法です!)、全ての UDP データグラムで以下のよ うなことが行われてしまいます。

これに対し TCP の対話中は VC はずっと残っています。

UDP は、同時に一つよりも多くのマシンに対して送られるマルチキャス トメッセージに使われるべきです。TCP では、アプリケーションは各目 的マシンに対して別々のコネクションをオープンして、各ターゲットマ シンに一回ずつメッセージを送らなくてはならなくなるでしょう。これ はあなたのアプリケーションが、すでに分かっているマシンとしか通信 できなくなるように制限されてしまいます。

5.2 「コネクト」ソケットと「非コネクト」ソケットとの違いは何ですか?

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

UDP ソケットが、bind() 呼出し後の通常の状態である非コネクト ソケットであれば、send()write() は使えません。それ は目的アドレスが利用できないためで、データを送るには sendto() しか使えません。

そのソケットに対して connect() を呼び出すと、単に指定された アドレスとポート番号を、希望の通信相手として記録します。これはつ まり、 send()write() が使えるようになるという意味 です。これらは、connect の呼び出しで与えられた目的アドレスとポー トをパケットの宛先として使用します。

5.3 connect() を呼び出すと、受信側のソケットの振る舞いに影響を与えますか?

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

はい、それは二つの点についてです。第一に、あなたの「コネクト相手」 からのデータグラムのみが返されるようになります。その他のものは全 て、このポートに到着していても、あなたに配送されることはありませ ん。

しかし最も重要なことは、UDP ソケットは ICMP エラーを受け取るため にはコネクトされていなければならないということです。"TCP/IP Illustrated, Volume 2" の 748-749 ページで、なぜそうであるかにつ いての生々しい詳細を全て与えてくれます。

5.4 ICMP エラーを「コネクトした」UDP ソケットから読み出すにはどうするのですか?

もしターゲットマシンが、要求されたポート番号から読み出しているプ ロセスが無いためにメッセージを捨てたとすると、あなたのマシンに ICMP メッセージが送られます。これによって、そのソケットに対する 次のシステムコールにおいて ECONNREFUSED が返されます。ICMP メッセージは配送されることが保証されていないので、この通知は一回 目の処理では受信されないかも知れません。

ICMP エラーを受信するためには、ソケットは「コネクト」されていな ければならない、ということを憶えておいてください。Linux では「非 コネクト」のソケットにおいても返される、ということを私は聞いたこ とがあり、Alan Cox 氏はそれを確認しました。もしあなたのアプリケー ションがそれに対して用意をしていないと、移植上問題が起こるかもし れません。それで、Alan は Linux カーネルの 2.0.0 以降で設定でき る SO_BSDCOMPAT フラグが追加されたのだと教えてくれました。

5.5 UDP メッセージが受信されたということをどうやって確認できるのでしょうか?

メッセージが受信されたら宛先からの折り返し確認を求めるように、プ ロトコルを設計しなければなりません。もちろんその確認は UDP によっ て送られるし、そしてそれも信頼することはできず、送り手に返ってこ ないかもしれません。もし送り手がある一定時間内に折り返し確認がで きなかったら、そのメッセージを再送しなければならないでしょう。 ひょっとすると一回では済まないかもしれません。さてここで受信側で 起こる問題は、そのメッセージはすでに受信されているかもしれないと いうことで、そのため重複したものを何らかの方法で捨てることが要求 されます。多くのプロトコルではメッセージに通し番号を振る機構を使っ て、受信側でこのメッセージが既に処理されたかどうかを知ることをで きるようにし、別の確認を返すことができるようにします。確認はその メッセージ番号も参照できるようにして送り手がどのメッセージが確認 されたのかを知ることができるようにしなければなりません。頭がこん がらがってきた? だから私はそれを TCP に押し付けているのです。

5.6 UDP メッセージが順序通り到着していることをどうすれば確認できるのでしょうか?

できません。 5.5 UDP メッセージが受信さ れたということをどうやって確認できるのでしょうか? で述べたよう に、番号付けシステムを使ってメッセージが順序通り処理されているこ とを確認するしかできることはありません。もし、メッセージが必ず受 信され、かつ順序通りに受信される必要があるのなら、TCPに切り替え ることを本気で考えるべきです。あなたがこの種のプロトコルの実装を、 TCP 関係者達がすでにやってきたことよりも上手に、多大な時間を投資 することなくできるとは、ほとんどありそうにないことです。

5.7 確認されなかったメッセージはどの程度の間隔で再送すべきでしょうか?

これを行なう最も簡単な方法は、単に一秒といった非常に小さい遅延時 間を選んで、ずっとそれを守ることです。これの問題は、LAN や相手側 のマシンに問題があった時に、ネットワークを無駄なトラフィックで混 雑させてしまうということです。そしてこの余分なトラフィックは問題 を悪化させるのにしか役に立ちません。

もっと良いテクニックとして、Richard Stevens 氏の "UNIX Network Programming" ( 1.6 [ある本の題名] という本のソースコードはどこから取得できますか? 参照) でソー スコードと一緒に記述されているものは、指数的に減少させる適応タイ ムアウトを使用する方法です。このテクニックはメッセージがホストに 到達するのにかかった時間についての統計情報を保持し、それに応じて タイムアウト値を調整します。また、ネットワークを無駄なデータグラ ムで溢れさせないように、メッセージが到達する度にタイムアウトを二 倍にします。Richard 氏は親切にもこの本のソースコードを Web 上に 置いてくださいました。彼のホームページ http://www.noao.edu/~rstevens を見てください。

5.8 データグラムの最初の部分しか届かないんですが、どうしてなんでしょうか?

これには二つのマシンにおけるデータグラムの最大サイズが複雑に関係 しているに違いありません。これはシステムと MTU(最大転送単位)とに 複雑に依存しています。"UNIX Network Programming" によれば、全て の TCP/IP の実装は、MTU の大きさに関わらず 576 バイトの最小 IP データグラムをサポートしなければなりません。IP ヘッダが 20 バイ トで UDP ヘッダが 8 バイトと仮定すると、UDP メッセージの安全な最 大サイズとしては 548 バイトが残ります。最大サイズは 65516 バイト です。プラットフォームによっては(MTU 値のため) データグラムを分 解し、そして相手側で再構成させる IP 断片化をサポートしているもの もありますが、しかし全ての実装がこれをサポートしているわけではあ りません。

この情報は、"UNIX Netowrk Programming" ( 1.6 [ある本の題名] という本のソースコードはどこ から取得できますか? 参照) の私の解釈から抜き出しています。

Andrew は大きな UDP メッセージに関して、以下の点を指摘しています:

別の問題は断片化です。データグラムが、それが送り出されていくネッ トワークインターフェースに対して大きすぎる場合、送信するホストは それを小さなパケットに断片化して、受信するホストで再構成されるで しょう。また、ルータが介在していれば、それらの ルータでもパ ケットを断片化する必要があるかもしれません。これはその断片を失う 機会を非常に増大させます(それはデータグラム全体の喪失を引き起こ します)。そういうわけで、大きな UDP データグラムは、ルータを越え るネットワークやインターネット本来の運用がされると思われるアプリ ケーションにおいては避けるべきです。

5.9 ソケットのバッファが思ったより早く満杯になってしまうのはどうしてでしょうか?

Paul W. Nelson 氏 ( nelson@thursby.com) より:

伝統的な BSD ソケットの実装では、UDP のようなアトミックなソケッ トは、データを mbuf のリストとして保持します。mbuf とはさまざま なプロトコルスタックによって共有される固定サイズのバッファです。 受信バッファのサイズを設定するとプロトコルスタックは、受信バッファ が、実際のバイト数ではなく mbuf 領域が何バイトあるか、を憶えてお きます。この方法が使われるのは、あなたが制御しているリソースは本 当に mbuf をいくつ使っているかであって、ソケットバッファに何バイ ト保持されているかではないからです(ソケットバッファは伝統的な意 味では本当はバッファではなく、mbuf のリストです)。

例: あなたの使っている UNIX では 256 バイトの小さなサイズの mbuf を使ってるとしましょう。受信ソケットバッファを 4096 と設定したと すると、ソケットバッファ内には 16 個の mbuf を入れることができま す。もしそれぞれが 10 バイトの UDP パケットを 16 個受信したとす ると、そのソケットバッファは一杯になってしまい、160 バイトのデー タを持つことになります。もしそれぞれが 200 バイトの UDP パケット を 16 個受信したとすると、ソケットバッファはやはり一杯になってし まいますが、3200 バイトのデータを持つことになります。 FIONREAD は総バイト数を返しますが、これはメッセージの数や mbuf のバイト数ではありません。そのため、これは受信バッファがど れくらい一杯であるかの指標としては良くありません。

そのうえ、もし 260 バイトの UDP メッセージを受信したとすると、二 つの mbuf を使い果たすことになり、ソケットバッファが満杯になるま でには 8 パケットしか受信できなくなります。この場合、ソケットバッ ファの中は、4096 バイトのうち 2080 バイトしか占められていないこ とになります。

この例は非常に単純化したもので、実際のソケットバッファのアルゴリ ズムでは他のパラメータも考慮に入ります。古いソケットの実装によっ ては 128 バイトの mbuf を使っているものもあるということに注意し てください。


Previous Next Table of Contents