前へ << モジュールを使ってみよう (3) | HTTP proxy サーバを作ってみよう >> 次へ |
use IO::Socket; @servers = qw(www.goo.ne.jp www.yahoo.co.jp www.asahi.com); foreach $server (@servers){ $socket = IO::Socket::INET->new(PeerAddr => $server, PeerPort => 80, Proto => 'tcp', ); print $socket "GET /index.html\r\n"; print $socket "Host: $server:80\r\n"; $socket->flush(); print <$socket>; }とすればよいです。しかし、これでは http://www.goo.ne.jp/ の読み込みが 終らないと http://www.yahoo.co.jp/ に進みません。http://www.yahoo.co.jp/ が 終らないと http://www.asahi.com/ に進みません。これを並行して読み込むことで、 実行時間の短縮を計りましょう。まずはスクリプトです。
1: #!/usr/local/bin/perl 2: 3: # $Id: http-client-parallel.pl,v 1.4 2005/09/03 21:23:21 68user Exp $ 4: 5: use IO::Socket; 6: use IO::Select; 7: 8: # 以下のサーバの web トップページを取得する 9: my @servers = qw(www.goo.ne.jp www.yahoo.co.jp www.asahi.com); 10: 11: my $port = 80; 12: 13: my $selecter = IO::Select->new; # select の前準備 14: 15: foreach my $server (@servers){ 16: 17: my $sock = IO::Socket::INET->new("$server:$port"); # 各サーバのソケットを生成 18: 19: $selecter->add($sock); # select の対象ソケットに追加 20: $sock2host{$sock} = "$server:$port"; # メッセージ表示用ハッシュ 21: 22: print $sock "GET / HTTP/1.0\r\n"; # データの送信 23: print $sock "Host: $server:$port\r\n"; 24: print $sock "\r\n"; 25: 26: $sock->flush(); # バッファをフラッシュ 27: } 28: 29: # 読み込むべきデータが残っているソケット数。初期値はサーバ数と同じ 30: my $last_sock = $#servers+1; 31: 32: # 読み込みが完了していないソケットが残っていたら 33: while ( $last_sock > 0 ){ 34: my ($readable_socks) = IO::Select->select($selecter, undef, undef, undef); 35: 36: # 読み込み可能なソケットがあれば、以下の foreach ループが実行される 37: foreach my $sock (@$readable_socks){ 38: my $len = sysread($sock, $buf, 4096); # ソケットから 4096 バイト読み込む 39: 40: if ( $len > 0 ){ # 1バイト以上読み込めた 41: 42: # 読み込んだ内容を整形して表示 43: 44: $buf =~ s/^(.{20}).*/$1/s; # 先頭 20バイト以降を捨てる 45: $buf =~ s/^/ /mg; # 先頭にスペースを挿入 46: $buf =~ s/[\r\n]//g; # 改行コードを省く 47: print "read ${len}bytes from $sock2host{$sock} $buf....\n"; 48: 49: } else { # 1バイトも読み込めなかった 50: 51: print "fin $sock2host{$sock}\n"; # そのソケットからの読み込みは終了 52: $selecter->remove($sock); # select の対象から外す 53: $sock->close(); # ソケットをクローズ 54: $last_sock--; # 残りソケット数を減らす 55: } 56: } 57: }
5: use IO::Socket; 6: use IO::Select;まず、IO::Socket モジュールと IO::Select モジュールを使うことを宣言します。
9: my @servers = qw(www.goo.ne.jp www.yahoo.co.jp www.asahi.com);この3サイトのトップページを読み込みます。@servers = qw(A B C) というのは @servers = ('A', 'B', 'C') と同じ意味です。いちいちクォートしたり カンマを書く必要がないので お勧めです。
13: my $selecter = IO::Select->new; # select の前準備IO::Select が返すオブジェクトを $selecter に代入します。
15: foreach my $server (@servers){ 16: 17: my $sock = IO::Socket::INET->new("$server:$port"); # 各サーバのソケットを生成 18: 19: $selecter->add($sock); # select の対象ソケットに追加 20: $sock2host{$sock} = "$server:$port"; # メッセージ表示用ハッシュ 21: 22: print $sock "GET / HTTP/1.0\r\n"; # データの送信 23: print $sock "Host: $server:$port\r\n"; 24: print $sock "\r\n"; 25: 26: $sock->flush(); # バッファをフラッシュ 27: }配列 @servers を順に $server に代入し、それぞれに対して
30: my $last_sock = $#servers+1;$last_sock には読み込みが完了していないソケットの数を代入します。 ここでは配列 @servers の要素数、この場合は 3 が代入されるはずです。
33: while ( $last_sock > 0 ){上で設定した $last_sock が 0 を越えている場合、 つまり読み込むべきソケットが残っている間、while ループを実行します。
34: my ($readable_socks) = IO::Select->select($selecter, undef, undef, undef);$selecter に登録したソケットの中で、読み込み可能となっている ソケットがあれば $readable_socks に返されます。その時点で全てのソケットが 読み込み可能となっていなければ (読み込むべきデータが届いていなければ)、 いずれかのソケットが読み込み可能になるまで IO::Select->select() から 帰ってきません。
37: foreach my $sock (@$readable_socks){ 38: my $len = sysread($sock, $buf, 4096); # ソケットから 4096 バイト読み込む$readable_socks は「配列へのリファレンス」が代入されますので、 @$readable_socks は「配列」です。各要素は「読み込み可能なソケット」です。
普通、データが届いていないソケットに対して sysread しようとするとブロック (データが届くまで動作が止まってしまうこと) されてしまいます。 しかし IO::Select で読み込み可能なソケットのみを調べ、それに対して sysread を行っていますので、 ブロックは起こりません。 よって、$readable_socks に入っているソケットは、
40: if ( $len > 0 ){ # 1バイト以上読み込めた 41: 42: # 読み込んだ内容を整形して表示 43: 44: $buf =~ s/^(.{20}).*/$1/s; # 先頭 20バイト以降を捨てる 45: $buf =~ s/^/ /mg; # 先頭にスペースを挿入 46: $buf =~ s/[\r\n]//g; # 改行コードを省く 47: print "read ${len}bytes from $sock2host{$sock} $buf....\n";読み込んだデータの先頭部分 20バイトを切り出し、表示します。 20バイトの部分がちょうど日本語の1バイト目と2バイト目にあたる場合は文字化けしますが、 サンプルということでそのままにしています。
49: } else { # 1バイトも読み込めなかった 50: 51: print "fin $sock2host{$sock}\n"; # そのソケットからの読み込みは終了 52: $selecter->remove($sock); # select の対象から外す 53: $sock->close(); # ソケットをクローズ 54: $last_sock--; # 残りソケット数を減らす 55: }$len==0 ならば、コネクションがクローズされた、 つまり読み込みが完了したということなので、$selecter->remove で select の対象ソケットから外し、ソケットをクローズします。 そして残りソケット数である $last_sock を1つ減らします。
read 2920bytes from www.goo.ne.jp:80 HTTP/1.1 200 OK Ser.... read 1460bytes from www.yahoo.co.jp:80 HTTP/1.0 200 OK Cont.... read 588bytes from www.yahoo.co.jp:80 kusai20001017/?http:.... read 1460bytes from www.goo.ne.jp:80 ="http://community.g.... read 137bytes from www.asahi.com:80 HTTP/1.1 200 OK Ser.... read 1460bytes from www.goo.ne.jp:80 ex.html">仕事</a><br.... read 1460bytes from www.yahoo.co.jp:80 > <br> </small> </.... read 1460bytes from www.goo.ne.jp:80 あなたの企業HPに.... read 1460bytes from www.asahi.com:80 <!-- home format #2 .... read 888bytes from www.asahi.com:80 82" height="64" usem.... read 499bytes from www.goo.ne.jp:80 ml" target="_blank">.... fin www.goo.ne.jp:80 read 1460bytes from www.yahoo.co.jp:80 .co.jp/shopping20001.... read 1460bytes from www.yahoo.co.jp:80 ks/literature_and_no.... read 1460bytes from www.yahoo.co.jp:80 ews/Newspapers/">新... read 1460bytes from www.asahi.com:80 <!--L.... read 1460bytes from www.yahoo.co.jp:80 f="/homeb/?http://ww.... read 463bytes from www.yahoo.co.jp:80 enter> <BR> <cente.... fin www.yahoo.co.jp:80 read 1130bytes from www.asahi.com:80 "/tech/feature/20001.... read 1460bytes from www.asahi.com:80 ref="http://mytown.a.... read 1181bytes from www.asahi.com:80 ubscribe2.html">w... read 301bytes from www.asahi.com:80 <a href="/event.ng/T.... read 1460bytes from www.asahi.com:80 <!-- L^C.... read 1460bytes from www.asahi.com:80 ews/international290.... read 892bytes from www.asahi.com:80 T(00:57)<br>.... read 1460bytes from www.asahi.com:80 hr noshade> </td> </.... read 1460bytes from www.asahi.com:80 N`G.... read 287bytes from www.asahi.com:80 area shape="rect" co.... fin www.asahi.com:80となります (途中一部を省略しています)。 並行して3ホストからデータを読み込めていることがわかりますね。
37: foreach my $sock (@$readable_socks){sysread では 4096 バイト分読もうとしていますが、最大で 2920 バイト、 最小で 301 バイト、大半は 1460 バイトとなっています。
sysread (システムコール read(2)) を使うと、 その時点で届いている分のデータを取得できます。1バイトかもしれませんし、 1KB かもしれません。1460 バイト読み込むことが多いのは、Ethernet における 最大フレーム長が 1500 バイトだからです。IP データグラムの最大サイズは 65535 バイトなのですが、それを転送する下位プロトコルでのサイズ制限のため 1500 バイトの IP データグラムに分割されるわけです。このうち 40 バイトは IP ヘッダのため、実データは残りの 1460 バイトになります。
もし sysread の代わりに read (標準 I/O ライブラリ fread(3)) を使うと、 指定した 4096 バイトを読み込むまで返ってきません。ライブラリ内部で 指定サイズ分のデータの到着を待ってしまうわけです。これでは 本当の並行動作にはなりませんので注意して下さい。
13: my $selecter = IO::Select->new; # select の前準備 14: 15: foreach my $server (@servers){ 16: 17: my $sock = IO::Socket::INET->new("$server:$port"); # 各サーバのソケットを生成 18: 19: $selecter->add($sock); # select の対象ソケットに追加 20: $sock2host{$sock} = "$server:$port"; # メッセージ表示用ハッシュ 21: 22: print $sock "GET / HTTP/1.0\r\n"; # データの送信 23: print $sock "Host: $server:$port\r\n"; 24: print $sock "\r\n"; 25:IO::Socket::INET->new では、ホスト名を IP アドレスに 変換するため、内部で gethostbyname が行われます。 そのとき DNS サーバに問い合わせを行いますが、IP アドレスが得られるか、 一定時間が経過しタイムアウトになるまで、その先の処理は行われません。
また、TCP コネクションを接続する部分は、内部で 3 way handshake が行われますが、 これも並行動作はできません。
ただし、ソケットに print で HTTP リクエストを送信する部分は、 相手側にパケットが届く前に次の処理が行われます。 これは、TCP がバッファリングしているからで、このバッファが 溢れたときは print 文で動作が止まり、相手の受信待ちになることがあります。 しかし、これくらいの小さなリクエストの場合は大丈夫でしょう。
前へ << モジュールを使ってみよう (3) | HTTP proxy サーバを作ってみよう >> 次へ |
ご意見・ご指摘は Twitter: @68user までお願いします。