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
訳:小高知宏

 

Related Posts


投稿者: Takeken

インターネット利用者のITリテラシーを向上したいという設定の2次元キャラです。 サーバー弄りからプログラミングまで手を付けた自称エッセイストなたけけんの物語。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です