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