Thanks for the responses. I ended up using IO::Select, and got it working. Posting the code here for posterity/review. Thanks!
#!/usr/bin/perl
use IO::Socket;
use IO::Select;
my $socket =new IO::Socket::INET->new (
LocalHost => "<ipaddress>",
LocalPort => '<port>',
Proto => 'tcp',
Listen => 1,
Reuse => 1,
);
die("Couldn't create socket! $!\n") unless $socket;
my $select = new IO::Select($socket);
while(@ready = $select->can_read) {
foreach $fh (@ready) {
if($fh == $socket) {
$new = $socket->accept;
$select->add($new);
my $host= $new->peerhost;
print "[Accepting connection from $host]\n";
}
else {
my $line = <$fh>;
$line =~ s/\s+$//;
if($line =~/^quit$/i) {
my $host = $fh->peerhost;
$select->remove($fh);
$fh->close;
print "[Connection from $host terminat
+ed\n";
}
else {
print $fh->peerhost, " said '$line'\n"
+;
print $fh "You said: '$line'\n";
}
}
}
}
Thanks for all the help! With multiple servers sending at the same time, it keeps track (using peerhost) which host sends the message, and of course the message contents.
| [reply] [d/l] |
Don't use buffered IO (e.g. read, readline aka <>) with select. You could get into a situation where data is waiting in Perl's buffer, and select wouldn't know anything about it.
Don't use IO that blocks after it reads all that's available to be read (e.g. read, readline aka <>) with select. It defies the purpose of using select.
You don't handle EOF, so you could end up having a handle that's permanently ready to read.
Fixed:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET qw( );
use IO::Select qw( );
sub process_msg {
my ($client, $msg) = @_;
chomp $msg;
my $host = $client->peerhost;
print "$host said '$msg'\n";
return lc($msg) eq 'quit;
}
my $server = IO::Socket::INET->new(
...
) or die("Couldn't create server socket: $!\n");
my $select = IO::Select->new($server);
my %bufs;
while (my @ready = $select->can_read) {
for my $fh (@ready) {
if ($fh == $server) {
my $client = $server->accept;
$select->add($client);
$bufs{fileno($client)} = '';
my $host = $client->peerhost;
print "[Accepted connection from $host]\n";
}
else {
our $buf; local *buf = \$bufs{fileno($fh)};
my $rv = sysread($fh, $buf, 64*1024, length($buf));
if (!$rv) {
my $host = $fh->peerhost;
if (defined($rv)) {
print "[Connection from $host terminated]\n";
} else {
print "[Error reading from host $host: $!]\n";
}
process_msg($fh, $buf) if length($buf);
delete $bufs{fileno($fh)};
$sel->remove($fh);
next;
}
while ($buf =~ s/\G(.*\n)//g) {
if (!process_msg($fh, "$1")) {
my $host = $fh->peerhost;
print "[Connection from $host terminated]\n";
delete $bufs{fileno($fh)};
$sel->remove($fh);
last;
}
}
}
}
}
| [reply] [d/l] [select] |
Thanks for the response! I knew it was a good idea to post my code here. I'm using the updated version, and it is working. I just need to go through and make sure I understand what the differences are... definitely a learning experience.
Thanks again!
| [reply] |
EDIT: Not sure what I was thinking, Monday morning I guess. I just replaced the \n chars with something else, searched for that after the transmission, then replaced them with \n's again afterward. It works great.
Ikegami, thanks again for the response.
I've been trying to decode this, and I think it mostly makes sense to me, but I need to be able to send multi-line messages (ie \n chars embedded) and want to terminate the sending connection with a specific character string. In other words, I'll have a datagram that looks something like this:
&&&hostname|blue*** (the start/end chars can be anything)
&&&ipaddress|1.2.3.4***
&&&kernelrev|123456-78***
&&&metastat|line1\nline2\nline3\n***
So I basically will be sending all types of data, but I want it to start paying attention when &&& is read, and read until ***, storing the contents in a var. (Probably will end up using a hash, if it matters)
I thought I had a good grip on how exactly that code works, but when I change what I thought I should change, it doesn't work as I expected. Any thoughts to send me in the right direction?
Much appreciated!
| [reply] |