Skip to content

Commit

Permalink
Add UDP support to Net::EmptyPort
Browse files Browse the repository at this point in the history
  • Loading branch information
SineSwiper committed Mar 18, 2013
1 parent a5939a7 commit fb9b30a
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 18 deletions.
64 changes: 46 additions & 18 deletions lib/Net/EmptyPort.pm
Expand Up @@ -11,22 +11,25 @@ our @EXPORT = qw/ empty_port check_port /;
# http://www.iana.org/assignments/port-numbers
sub empty_port {
my $port = do {
if (@_) {
if (defined $_[0]) {
my $p = $_[0];
$p = 49152 unless $p =~ /^[0-9]+$/ && $p < 49152;
$p;
} else {
50000 + int(rand()*1000);
}
};
my $proto = $_[1] ? lc($_[1]) : 'tcp';

while ( $port++ < 60000 ) {
next if check_port($port);
# Remote checks don't work on UDP, and Local checks would be redundant here...
next if ($proto eq 'tcp' && check_port($port));

my $sock = IO::Socket::INET->new(
Listen => 5,
(($proto eq 'udp') ? () : (Listen => 5)),
LocalAddr => '127.0.0.1',
LocalPort => $port,
Proto => 'tcp',
Proto => $proto,
(($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
);
return $port if $sock;
Expand All @@ -35,29 +38,43 @@ sub empty_port {
}

sub check_port {
my ($port) = @_;

my $remote = IO::Socket::INET->new(
Proto => 'tcp',
PeerAddr => '127.0.0.1',
PeerPort => $port,
);
if ($remote) {
close $remote;
my $port = $_[0];
my $proto = $_[1] ? lc($_[1]) : 'tcp';

# for TCP, we do a remote port check
# for UDP, we do a local port check, like empty_port does
my $sock = ($proto eq 'tcp') ?
IO::Socket::INET->new(
Proto => 'tcp',
PeerAddr => '127.0.0.1',
PeerPort => $port,
) :
IO::Socket::INET->new(
Proto => $proto,
LocalAddr => '127.0.0.1',
LocalPort => $port,
(($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
)
;

if ($sock) {
close $sock;
return 1;
}
else {
return 0;
}

}

sub wait_port {
my ($port, $sleep, $retry) = @_;
my ($port, $sleep, $retry, $proto) = @_;
$retry ||= 100;
$sleep ||= 0.1;
$proto = $proto ? lc($proto) : 'tcp';

while ( $retry-- ) {
if ($^O eq 'MSWin32' ? `$^X -MNet::EmptyPort -echeck_port $port` : check_port( $port )) {
if ($^O eq 'MSWin32' ? `$^X -MNet::EmptyPort -e"check_port $port,'$proto'"` : check_port( $port, $proto )) {
return 1;
}
Time::HiRes::sleep($sleep);
Expand All @@ -73,7 +90,7 @@ __END__
=head1 NAME
Net::EmptyPort - find a free TCP port
Net::EmptyPort - find a free TCP/UDP port
=head1 SYNOPSIS
Expand All @@ -89,7 +106,7 @@ Net::EmptyPort - find a free TCP port
=head1 DESCRIPTION
Net::EmptyPort helps finding an empty TCP port.
Net::EmptyPort helps finding an empty TCP/UDP port.
=head1 METHODS
Expand All @@ -109,13 +126,24 @@ But you want to use another range, use a following form:
# 5963..65535
my $port = empty_port(5963);
You can also find an empty UDP port by specifying the protocol as
the second parameter:
my $port = empty_port(1024, 'udp');
# use 49152..65535 range
my $port = empty_port(undef, 'udp');
=item check_port
my $true_or_false = check_port(5000);
Checks if the given port is already in use. Returns true if it is in use (i.e. if the port is NOT free). Returns false if the port is free.
=item wait_port($port:Int[, $sleep:Number, $retry:Int])
Also works for UDP:
my $true_or_false = check_port(5000, 'udp');
=item wait_port($port:Int[, $sleep:Number, $retry:Int, $proto:String])
Waits for a particular port is available for connect.
Expand Down
42 changes: 42 additions & 0 deletions t/11_net_emptyport.t
@@ -0,0 +1,42 @@
use warnings;
use strict;
use Test::More tests => 11;
use Net::EmptyPort qw(empty_port check_port);
use IO::Socket::INET;

my ($port, $new_port, $sock);

foreach my $proto_uc ('TCP', 'UDP') {
my $proto = lc $proto_uc;
$port = empty_port(5000, $proto);
ok( $port, $proto_uc.': port found via empty_port' );

$sock = new_ok( 'IO::Socket::INET' => [
(($proto eq 'udp') ? () : (Listen => 5)),
LocalAddr => '127.0.0.1',
LocalPort => $port,
Proto => $proto,
(($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
] );

$new_port = empty_port($port, $proto);
isnt( $new_port, $port, $proto_uc.': different port found via empty_port' );

$sock->close;
$sock = new_ok( 'IO::Socket::INET' => [
(($proto eq 'udp') ? () : (Listen => 5)),
LocalAddr => '127.0.0.1',
LocalPort => $port,
PeerAddr => '127.0.0.1',
PeerPort => $new_port,
Proto => $proto,
(($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
] );

is( check_port($port) , 0, $proto_uc.': check_port($port) == 0') if ($proto eq 'udp');
is( check_port($new_port), 0, $proto_uc.': check_port($new_port) == 0');

$sock->close;
}

1;

0 comments on commit fb9b30a

Please sign in to comment.