Test::TCPを使って何故かハマった
http://d.hatena.ne.jp/tokuhirom/20080817/1218953905 を見つつ、試しに書いてみた
#!/usr/bin/perl use strict; use warnings; use Test::TCP; use IO::Socket::INET; test_tcp( client => sub { my $port1 = shift; test_tcp( client => sub { my $port2 = shift; while (1) { sleep 1; } }, server => sub { my $port2 = shift; server($port2); }, ); }, server => sub { my $port1 = shift; server($port1); }, ); sub server { my $port = shift; warn $port; my $sock = IO::Socket::INET->new( LocalPort => $port, LocalAddr => '127.0.0.1', Proto => 'tcp', Listen => 5, Type => SOCK_STREAM, ) or die "Cannot open server socket: $!"; while (my $remote = $sock->accept) { while (my $line = <$remote>) { print {$remote} $line; } } }
$ perl hoge.pl 10001 at hoge.pl line 30. 10002 at hoge.pl line 30.
test_tcpの中で2つサーバを動かしても、空いているポートを自動的に見つけて振り分けてくれるらしい。
ここで、サーバをAnyEvent::Socketを使ったものにしてみた。
sub server { my $port = shift; warn $port; my $cv = AnyEvent->condvar; tcp_server undef, $port, sub {}; $cv->recv; }
すると、
$ perl fuga.pl 10001 at fuga.pl line 30. 10001 at fuga.pl line 30. bind: Address already in use at fuga.pl line 33
portがぶつかってしまった。
でも、これが起こるのは自分のMacBookの環境で、他のLinux環境ではちゃんと別々のportに振り分けられた。
何が起こってるの?
AnyEvent::Socket::tcp_server、Test::TCP::empty_portの実装を少し追ってみた。
tcp_server の中身はsocket作ってbindしてlistenしたり、とやっているらしい。簡略化すると
use Socket qw(AF_INET SOCK_STREAM inet_aton sockaddr_in); use IO::Socket::INET; my $fh; socket $fh, AF_INET, SOCK_STREAM, 0 or die $!; my $name = sockaddr_in($port, inet_aton($host)); bind $fh, $name or die $!; listen $fh, 128 or die $!;
というカンジ?
一方、Test::TCP::empty_portでは、IO::Socket::INETを使ってsocketを作成できるかどうかでportの空きを見ているようだ。
my $sock = IO::Socket::INET->new( Listen => 5, LocalAddr => '127.0.0.1', LocalPort => $port, Proto => 'tcp', (($^O eq 'MSWin32') ? () : (ReuseAddr => 1)), ); return $port if $sock;
まぁこの中でも同じようにsocket作ってbindしてlistenしてるのか…
tcp_serverの第1引数
AnyEvent::Socketのtcp_serverについてのPODをよく読んでみると、
For internet sockets, $host must be an IPv4 or IPv6 address (or "undef", in which case it binds either to 0 or to "::", depending on whether IPv4 or IPv6 is the preferred protocol, and maybe to both in future versions, as applicable).
って書いてある。後半の意味がよく分からないのだけど…
とりあえず$hostにはundefを指定できるけど、この場合 0というアドレスにbindされることになるらしい。'127.0.0.1'とは違うのか!
試しに最初にハマったやつを
sub server { my $port = shift; warn $port; my $cv = AnyEvent->condvar; tcp_server '127.0.0.1', $port, sub {}; $cv->recv; }
って書き直したらちゃんと動くようになった…!
環境による違い?
なんでMacではダメでLinuxではアレで動いていたんだろう?という疑問。
#!/usr/bin/perl use strict; use warnings; use AnyEvent::Socket; my $cv = AnyEvent->condvar; tcp_server undef, 10001, sub {}; tcp_server '127.0.0.1', 10002, sub {}; $cv->recv;
というコードを走らせて、裏で使用portを調べてみる。
- Macの場合
$ netstat -an | grep 1000. tcp4 0 0 127.0.0.1.10002 *.* LISTEN tcp4 0 0 *.10001 *.* LISTEN
- Linuxの場合
$ netstat -an | grep 1000. tcp 0 0 0.0.0.0:10001 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:10002 0.0.0.0:* LISTEN
Local Addressの扱いが環境によって違うっぽい。これが$host = undefでの挙動の違いになる…のかな?
ちょっとマジメにTCPの勉強したほうが良さそうだ… orz