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;
こんな感じだと、以前のサーバー日記に近いかなぁ。
参考サイト
参考文献
TCP/IPソケットプログラミングC言語編
共著:Michael J.Donahoo/Kenneth L. calvert
訳:小高知宏