You are correct. I must have misread the numbers.
However, there is no problem. Here are a sample server and client that do bidirection communication over 4 socket pairs. The server waits for an initial heartbeat from the client, then starts sending data, and it will stop sending data if it hasn't seen a heartbeat for 10 seconds. There's of course lots of room for improvement, but it does show bidirectional communication is not a problem.
First, the server:
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use IO::Socket;
my $INTERVAL = 10;
my $MAX_BUFSIZE = 512;
my $NO_CONTACT = 0;
my $SENDING = 1;
my $LOST_CONTACT = 2;
my $server = '192.168.0.1';
my @server_ports = (8020, 8019, 8008, 8003);
my @sockets = map {
my $socket = IO::Socket::INET::->new(
LocalPort => $_,
LocalAddr => $server,
Proto => 'udp',
) or die $!;
$socket;
} @server_ports;
my @states = ($NO_CONTACT) x @sockets;
my @heartbeats = (0) x @sockets;
my @counts = (0) x @sockets;
my $Rbits = "";
my $Ebits = "";
vec($Rbits, fileno($_), 1) = 1 for @sockets;
vec($Ebits, fileno($_), 1) = 1 for @sockets;
while (1) {
select(my $rbits = $Rbits, undef, my $ebits = $Ebits, 1);
for (my $i = 0; $i < @sockets; $i ++) {
my $socket = $sockets[$i];
my $fileno = fileno($socket);
my $state = $states[$i];
my $heartbeat = $heartbeats[$i];
if (vec($ebits, $fileno, 1)) {
say "Got an error on channel $i. I'm out of here";
exit 1;
}
if (vec($rbits, $fileno, 1)) {
my $sender = $socket->recv(my $buffer, $MAX_BUFSIZE);
if (length $buffer) {
my ($port, $remote) = sockaddr_in($sender);
$remote = inet_ntoa($remote);
say "Got HB from $remote:$port";
$heartbeat = $heartbeats[$i] = time;
if ($state == $NO_CONTACT) {
#
# Upgrade the socket now we know the remote
#
vec($Rbits, fileno($socket), 1) = 0;
vec($Ebits, fileno($socket), 1) = 0;
undef $sockets[$i];
undef $socket;
$socket = $sockets[$i] = IO::Socket::INET::->new(
LocalPort => $server_ports[$i],
LocalAddr => $server,
PeerAddr => $remote,
PeerPort => $port,
Proto => 'udp',
) or die $!;
vec($Rbits, fileno($socket), 1) = 1;
vec($Ebits, fileno($socket), 1) = 1;
$state = $states[$i] = $SENDING;
}
}
}
#
# Out of time?
#
if ($state == $SENDING && $heartbeat + $INTERVAL < time) {
say "Channel $i is dead. Bye!";
exit 1;
}
#
# Send data?
#
if ($state == $SENDING) {
foreach (1 .. int rand 10) {
my $count = ++$counts[$i];
say "Send packet $count on channel $i";
$socket->send($count);
}
}
}
}
__END__
And the client:
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
use autodie;
use IO::Socket;
my $server = '192.168.0.1';
my $client = '192.168.0.12';
my @server_ports = ( 8020, 8019, 8008, 8003);
my @client_ports = (53036, 53037, 53038, 53039);
my $INTERVAL = 10;
my $MAX_BUFSIZE = 512;
my @sockets;
for (my $i = 0; $i < @server_ports; $i ++) {
$sockets[$i] = IO::Socket::INET::->new(
LocalPort => $client_ports[$i],
LocalAddr => $client,
PeerPort => $server_ports[$i],
PeerAddr => $server,
Proto => 'udp',
) or die $!;
}
my $Rbits = "";
my $Ebits = "";
vec($Rbits, fileno($_), 1) = 1 for @sockets;
vec($Ebits, fileno($_), 1) = 1 for @sockets;
#
# Now loop. If there's data to read, read it. If it's time to send a
# heartbeat, do so.
#
my @heartbeats = (0) x @sockets;
while (1) {
for (my $i = 0; $i < @sockets; $i++) {
if (time >= $heartbeats[$i] + $INTERVAL - 1) {
#
# Initialize contact, or keep alive
#
say "Send HB on channel $i";
$sockets[$i]->send("HB");
$heartbeats[$i] = time;
}
}
select(my $rbits = $Rbits, undef, my $ebits = $Ebits, 1);
for (my $i = 0; $i < @sockets; $i++) {
my $socket = $sockets[$i];
my $fileno = fileno($socket);
if (vec($ebits, $fileno, 1)) {
say "Got an error on channel $i. I'm out of here";
exit 1;
}
if (vec($rbits, $fileno, 1)) {
#
# Read messages, if any
#
$socket->recv(my $buffer, $MAX_BUFSIZE);
next unless length $buffer;
say "Received packet $buffer on channel $i";
}
}
}
__END__