PerlでUDPEchoServer

TCP/IP Socket in cをPerlで振り返るシリーズの第4弾です。

今回はUDPのEchoServerです。クライアントから来たメッセージを返すという単純なものですな。

C言語でやってきた事と同じことをPerlでやっているだけですが、これがなかなか勉強になる事が多いです。Perl入門とかをやるよりよっぽど吸収率がいいとおもいます。

実行結果ですが、クライアントのプログラムの場合と同じく、TCPで作ったEchoServerをベースにしまして、UDPには不要な部分をとっぱらい、UDPの場合なのでrecvとsendに書き換えたのでござるが、1回でとりあえず動いてしまったのでまたもや過程の部分が大幅に端折られますorz

だが、先はまだまだ続いているのでご安心を・・・
まずは、実行すると、エコーが返ってきているけど、エラーが出ているよというところから始まります。
エコーとエラーが似ているので文字をでかくしました。

まずはクライアントの実行結果

# perl UDPEchoClient.pl 192.168.24.61 Hello 500               [/root/perl]
192.168.24.61の500番にいってきます。
Helloが返ってきました。

サーバーの実行結果


# perl UDPEchoServer.pl 500                                    [/root/perl]
Helloが来たよ。
Handling client 192.168.24.63
recv() on closed socket SOCKET at UDPEchoServer.pl line 36.
recv failed 不正なファイル記述子です

ご覧のように動作はしているようなのだけど、エラーが出ているんです。

よ~~く見ると分かるんだけど、これって2巡目でエラーが出ている事が分かります。

デバッグだという事で、ちょこちょこプログラムを変えながら実行しているとソケットを閉じているからという事が分かりましたorz
TCP/IP Socket in Cにも書いてあったんだけど、TCPサーバではクライアント毎にacceptを実行して、新しいソケットを取得しますが、UDPでは1つのソケットをすべての通信に使います。
分かったらバカな間違いですよね。
わたしって、ほんとバカ。

続きまして、なんとなしにshutdown(SOCKET, 1)をコメントアウトしたら

# perl UDPEchoServer.pl 100                                    [/root/perl]
Handling client 192.168.24.63
Helloが来たよ。
Handling client 192.168.24.63
が来たよ。
send failed

こうなりました。

なんでやねーん。と思いましたが、これがゴールへの道をぐぐっと近づけました。
まさにたなぼたですね。ほんとにそう思いました。

recv関数について調べてみると

通常、引数で指定した受信バイト数より実際に受信した量が少ない場合
待機せず直ちにデータを返します。

という事なので、が来たよ。が吐き出されているのはおかしいのだ。

さらにデバッグを続けていって、なんとなくだけどshutdown(SOCKET, 0)を解除してみた。
これは左がディスクリプタで右がフラグですが、0は読み込み、1は書き込みです。
クライアントのプログラムの場合はこのshutdownをつけないと送り終わった、受け終わったという処理がされなかったのですが、それはあくまでクライアントの話だったようだ。

クライアント側の実行結果

# perl UDPEchoServer.pl 100                                    [/root/perl]
Handling client 192.168.24.63
Helloが来たよ。
Handling client 192.168.24.63
Helloが来たよ。
send failed パイプが切断されました

えーと、クライアント側のsendがエラーなので、サーバー側で受け取りができなかったという事だろうね。

ここまで来たら答えは分かったも同然。
クライアント側では重要な関数だったshtudownが通信を妨害していたのでした。
サーバー側は送られてきた信号を返すだけなので、メッセージの終わりですよという関数は必要なかったんだ。

では最後にソース UDPEchoServer.pl

#UCPEchoサーバープログラム
#!/usr/bin/perl

use strict;
use warnings;
use Socket;
use IO::Handle;

#キューの最大数
my $ECHOMAX = 255;

#引数の数をチェック
if($#ARGV != 0)
{
        print "Usage : Program <Server Port>\n";
        exit(1);
}

#ポート番号を指定
my $port = $ARGV[0];

#TCPソケット生成
socket(SOCKET, PF_INET, SOCK_DGRAM, 0) or die "Socket failed $!\n";

#アドレス構造体を作成
#ポートとアドレスをパック
my $sock_addr = pack_sockaddr_in($port, INADDR_ANY);

#バインド
bind(SOCKET, $sock_addr) or die "bind failed $!\n";

while(1)
{
        my $buf;
        my $cldata = recv(SOCKET, $buf, $ECHOMAX, MSG_WAITALL) or die "recv $!";

        #つけちゃダメ
        #shutdown(SOCKET, 0);

        #クライアントの情報を取得
        my $client_addr = unpack_sockaddr_in($cldata);

        #atonの逆すなわちバイナリから変換。
        my $client_iaddr = inet_ntoa($client_addr);
        print "Handling client $client_iaddr\n";
        print "$bufが来たよ。\n";

        #接続したサーバへメッセージ送信
        send(SOCKET, $buf, 0, $cldata) or die "send failed $!\n";

        #つけちゃダメ
        #shutdown(SOCKET, 1);
}
exit;

 

参考サイト

Perldoc.jp

参考文献

TCP/IPソケットプログラミングC言語編
共著:Michael J.Donahoo/Kenneth L. calvert
訳:小高知宏

 

Similar Posts:


Leave a Reply

Your email address will not be published. Required fields are marked *