PerlでUDPEchoClient

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

今までの反省を踏まえて今度は過程も書いてみようと思いますだ。
ベースはTCPEchoServer.plを使うんで、まずはSOCK_STREAMをSOCK_DGRAMに書き換えただけでどんな挙動になるか見てみる。

socket(SOCKET, PF_INET, SOCK_STREAM, 0) or die "Socket faild\n";
socket(SOCKET, PF_INET, SOCK_DGRAM, 0) or die "Socket faild\n";

挙動が分からないので別のホストでエコーサーバーを立ち上げてtcpdumpでudpを監視してるんだが、プログラム側でもエラー出ないし、うんともすんともなんとも言わなかったとも。

続いてTCPには必要ないコネクトを削除する。
TCP/IPだと閉ざされた扉は開けないなぁなんて思ったけど、どんなに大きな防火壁があっても超えてみせるからきっと っていう気持ちが大事。

Printだと何も起こらないようなので、おとなしくsendでいこうと思います。
んで、sendでやったらデータはサーバーに来ました。

※この件についてはPerldocに記述がありました。

UDP はバイトストリーム ではなく 、そのように扱うべきでもありません。
これは stdio(つまり print() やその親戚) のような内部バッファリング
付きの I/O 機構を特に扱いにくくします。 以下の例のように、syswrite() か、
よりよい send() を使ってください。

という事でした。Perldocさんは Perl の公式ドキュメント、モジュールドキュメントを日本語に翻訳したものを表示するサイトです。

では、サーバー側のtcpdumpの様子。

Handling client 192.168.24.63
21:52:37.998026 IP 192.168.24.63.57744 > test2.net.sieve-filter: UDP, length 1
21:52:37.998109 IP test2.net.sieve-filter > 192.168.24.63.57744: UDP, length 1

当然ながら今のままじゃクライアント側でメッセージは見れない。

# perl UDPEchoClient.pl 192.168.24.61 hello 2000              [/root/perl]
192.168.24.61の2000番にいってきます。

てなわけで、recvをしよう。
こんな感じで作ってみました。

send(SOCKET, $EchoWord, 0, $sock_addr) or die "send failed $!\n";

my $buf;
recv(SOCKET, $buf, 4, 0) or die "recv failed $!\n";
print "$buf\n";

結果 おっと、バッファが足りない!

# perl UDPEchoClient.pl 192.168.24.61 hello 2000              [/root/perl]
192.168.24.61の2000番にいってきます。
hell

・・・

気を取り直して、完成です。

my $buf;
recv(SOCKET, $buf, 32, 0) or die "recv failed $!\n";
print "$bufが返ってきました。\n";
shutdown(SOCKET, 0);

実行結果

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

こっちはサーバーのtcpdump、lengthが増えたね。

Handling client 192.168.24.63
22:06:01.343056 IP 192.168.24.63.44227 > test2.net.sieve-filter: UDP, length 5
22:06:01.343148 IP test2.net.sieve-filter > 192.168.24.63.44227: UDP, length 5

さいごにソース UDPEchoServer.pl

#UDPEchoクライアントプログラム
#!/usr/bin/perl

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

##引数の数のチェック
if( $#ARGV < 1 || $#ARGV > 2)
{
        print "Usage: #program <Server IP> <Echo Word> [<Echo Port>]\n";
        exit(1);
}

#コマンドラインからサーバ、メッセージを引っ張る。
my $ServerIP = $ARGV[0];
my $EchoWord = $ARGV[1];

#指定があればポート番号を代入。
my $port;
if($#ARGV == 2)
{
        $port = $ARGV[2];
}
else
{
        $port = 7;
}

#UDPデータグラムソケットを作成
socket(SOCKET, PF_INET, SOCK_DGRAM, 0) or die "Socket faild\n";

#アドレス構造体を作成
#ホスト名orIPアドレスをバイナリに変換
#ポートとIPアドレス(バイナリ)をパック
my $addr = inet_aton($ServerIP) or die "pack error\n";
my $sock_addr = pack_sockaddr_in($port, $addr);

print "$ServerIPの$port番にいってきます。\n";
#今回もオートフラッシュ不要
#SOCKET->autoflush;

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

#送信完了を送る(重要)
shutdown(SOCKET, 1);

#文字列をサーバから受信
my $buf;
recv(SOCKET, $buf, 32, 0) or die "recv failed $!\n";
print "$bufが返ってきました。\n";
shutdown(SOCKET, 0);

#ソケットを閉じる
close(SOCKET);

#正常に終了
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 *