前へ << UDP を使ってみよう (2) | UDP を使ってみよう (5) >> 次へ |
25: $sock_addr = pack_sockaddr_in($port, $iaddr);まず、接続先を表すソケットアドレス構造体を作ります。
38: if ( ! send(SOCKET, "Hello $i", 0, $sock_addr) ){そしてソケットアドレス構造体を渡しています。
TCP では socket を作って、connect してからデータ送信をしたのですが、 UDP では connect しません。connect しないということは、 そもそも接続先が存在するかどうかを知ることさえできないという意味です。
例えば
% ./udp-client-1.pl localhost 45678 (デタラメなポート番号を指定) % ./udp-client-1.pl 192.168.111.222 45678 (デタラメな IP アドレスを指定)などと、(サーバを起動せず) デタラメなポート番号を指定してクライアントを実行してみて下さい。 するとエラーが全く報告されません。send のエラーチェックはしているにもかかわらず、です。 UDP は、
37: for ( $i=1 ; $i<=$num_of_senddata ; ){ 38: if ( ! send(SOCKET, "Hello $i", 0, $sock_addr) ){ 39: # エラーが発生した 40: 41: if ( $! == Errno::ENOBUFS ){ 42: # 送信バッファがいっぱい (ENOBUFS) ならリトライ 43: 44: $num_of_enobufs++; 45: next; 46: 47: } else { 48: # ENOBUFS 以外なら終了 49: die "send に失敗しました ($i)。$!\n"; 50: } 51: } 52: $i++; 53: }send でエラーが発生したときは $! を見ます。 もし $! の内容が ENOBUFS だったら、再送します。 ENOBUFS というのは「送信バッファが一杯になった」というエラーです。
送信バッファというのは送信側 OS の内部にあるバッファのことで、 send したデータはいったん送信バッファに蓄えられ、その後 外部に送信されます。send を連続して呼び出すと、 外部への送信が間に合わず、ENOBUFS が発生します。
このプログラムでは ENOBUFS が発生すると、 再度同じデータを送信しています。
同じマシン内で udp-server-1.pl と udp-client-1.pl を実行すると、 ENOBUFS は一度も発生しないかもしれません (FreeBSD 4.4-RELEASE の同一マシン内で通信すると ENOBUFS は発生しませんでした)。
udp-server-1.pl を X68000.startshop.co.jp で実行し、 別マシンからインターネット経由で udp-client-1.pl を実行したところ、
% ./udp-client.pl X68000.startshop.co.jp 7000 X68000.startshop.co.jp:7000 に対して send を 100000 回実行しました。 ENOBUFS の発生回数 660182となりました。10万回の送信に対して ENOBUFS が 66万回というのはかなり多いですが、 これは以下の理由によります。
open(IN, "/foo/bar/baz.txt") or die "$!\n";と書きます。もしオープンに失敗すると
No such file or directoryと出力して終了しますので、 「エラー発生時に $! に文字列がセットされる」と考えがちですが、違います。 $! は文字列コンテキストではエラー文字列を返し、 数値コンテキストではエラー番号を返します。エラー番号というのは OS ごとに決められている番号で、FreeBSD ならば
#define ENOBUFS 55 /* No buffer space available */とありますので、
if ( $! == 55 ){ ... } if ( $! eq "No buffer space available" ){ ... }は (FreeBSD においては) 等価です。
ただし、ソースにマジックナンバを直接書くとソースの可読性が低くなりますし、 他の OS では 55 が EONBUFS を表すとは限りません (例えば Solaris9 では ENOBUFS は 132 です)。 エラー文字列も「No buffer space available」ではない OS があるでしょう。もしかしたら日本語 locale を使っていると (例えば LANG=ja としている場合)、 「バッファ容量が不足しています」 などと日本語のエラーメッセージがセットされているかもしれません。
そこで、ここでは use Errno として Errno パッケージを利用し、
if ( $! == Errno::ENOBUFS ){ ... }としています。詳しい使い方は perldoc Errno して下さい。
なお、$! というのはシステムコールがセットするグローバル変数であることに注意して下さい。 内部でシステムコールを呼ばないライブラリ (例えば sprintf) は $! をセットしません。
C 言語を知っている方向けに書いておくと、perl では数値コンテキストのとき $! は errno の値が入っており、文字列コンテキストのときは perror がセットする文字列が入っています。
誰が破棄するかと言えば、それはルータです。 ルータはインタフェースを複数持っており、 ルーティングテーブルを参照して適切なインタフェースにデータを転送しなければなりません。 転送しないうちにどんどん新たなデータが入ってくる場合、データは破棄されます。
前へ << UDP を使ってみよう (2) | UDP を使ってみよう (5) >> 次へ |
ご意見・ご指摘は Twitter: @68user までお願いします。