J
John Chambers
I've grabbed a number of perl TCP server/client pairs, and experimented with
getting them to do some simple request-response sequences. A bizarre flushing
failure has popped up in all of them, and no amount of futzing with $| and
autoflush seems to make them work.
Here's the code for one of the simplest pairs.
=====================================
The TCPserver.pl program is:
#!/usr/local/bin/perl -w
use IO::Socket;
use Net::hostent;
$port = 4217; # pick something not in use
select STDOUT; $| = 1;
($P = $0) =~ s".*/"";
$V = $ENV{"V_$P"} || 2; # Verbose level
$prompt = "Command? ";
$EOL = "\015\012"; # Paranoia
$server = IO::Socket::INET->new( Proto => 'tcp',
LocalPort => $port,
Listen => SOMAXCONN,
Reuse => 1);
die "can't setup server ($!)" unless $server;
print "[Server $0 accepting clients on port $port]$EOL";
while ($client = $server->accept()) {
$client->autoflush(1);
select $client; $| = 1; select STDOUT;
print $client "Welcome to $0; type help for command list.$EOL";
$hostinfo = gethostbyaddr($client->peeraddr);
printf "[Connect from %s]$EOL", $hostinfo->name || $client->peerhost;
select STDOUT;
print "SEND \"$prompt\"$EOL";
print $client $prompt;
print "SENT \"$prompt\"$EOL";
while ($line = <$client>) {
print "RCVD \"$line\"$EOL" if $V>1;
$line =~ s/[\r\n]+$//;
next unless $line; # blank line
# autoflush $client 1; # Does this help? Nope
if ($line =~ /quit|exit/i) { last; }
elsif ($line =~ /date|time/i) { printf $client "%s$EOL", scalar localtime; }
elsif ($line =~ /who/i ) { print $client `who 2>&1`; }
elsif ($line =~ /cookie/i ) { print $client `/usr/games/fortune 2>&1`; }
elsif ($line =~ /motd/i ) { print $client `cat /etc/motd 2>&1`; }
else {
print $client "Commands: quit date who cookie motd$EOL";
}
} continue {
select STDOUT;
print "SEND \"$prompt\"$EOL";
print $client $prompt;
print "SENT \"$prompt\"$EOL";
}
close $client;
}
==============================
And here's the TCPclient.pl program:
#!/usr/local/bin/perl -w
use strict;
use IO::Socket;
my ($host, $port, $kidpid, $server, $line);
my $EOL = "\015\012"; # Paranoia
select STDOUT; $| = 1;
my $P = $0; $P =~ s".*/"";
my $V = $ENV{"V_$P"} || 2; # Verbose level
if (@ARGV <1) {push @ARGV, 'localhost'} # Default to local server
if (@ARGV <2) {push @ARGV, '4217'} # Default port for TCPserver.pl
($host, $port) = @ARGV;
# create a tcp connection to the specified host and port
$server = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
or die "can't connect to port $port on $host: $!";
$server->autoflush(1); # so output gets there right away
#autoflush $server 1;
select $server; $| = 1; select STDOUT;
print "[Connected to $host:$port]$EOL";
# split the program into two processes, identical twins
die "can't fork: $!" unless defined($kidpid = fork());
# the if{} block runs only in the parent process
if ($kidpid) { # copy the socket to standard output
print "READ ...$EOL" if $V>1;
while (defined ($line = <$server>)) {
print "RCVD \"$line\"$EOL" if $V>1;
print STDOUT $line;
}
kill("TERM", $kidpid); # send SIGTERM to child
} else { # the else{} block runs only in the child process
# copy standard input to the socket
while (defined ($line = <STDIN>)) {
print "SEND \"$line\"$EOL" if $V>1;
print $server $line;
print "SENT \"$line\"$EOL" if $V>1;
}
}
=====================================
Some readers may recognize these from online sources. Anyway, the Server's
"Command? " prompt is sent to the client, but the client doesn't receive it
at all until the server sends something that ends with a newline (which was
coded "\n" in an earlier version, and "\015" here as a variant). This despite
the setting of $| to 1 for every file in sight, and the use of autoflush(1)
for the sockets also. I tried the autoflush function, too, though it's
commented out here. None of these attempts to subvert the buffering works;
the server's prompt requires a newline for it to be read by the client.
For example, I started the two programs up in two windows, they both printed
their startup messages, the server produced a "SEND ..." and "SENT ..." message
for the prompt, and both were hung. In the client window, I hit Enter, and then
types a "date" command plus an Enter. On the server side, the output was:
=====================================
: ./TCPserver.pl
[Server ./TCPserver.pl accepting clients on port 4217]
[Connect from localhost]
SEND "Command? "
SENT "Command? "
RCVD "
"
SEND "Command? "
SENT "Command? "
RCVD "date
"
SEND "Command? "
SENT "Command? "
=====================================
That looks like exactly what you'd expect.
Meanwhile, over on the client side, what's on the screen is:
=====================================
: ./TCPclient.pl
[Connected to localhost:4217]
READ ...
RCVD "Welcome to ./TCPserver.pl; type help for command list.
"
Welcome to ./TCPserver.pl; type help for command list.
[Here I hit the Enter key]
SEND "
"
SENT "
"
date [Here I sent an actual command]
SEND "date
"
SENT "date
"
RCVD "Command? Command? Wed Jan 21 17:17:03 2004 <=== The prompts finally appear!!
"
Command? Command? Wed Jan 21 17:17:03 2004
=====================================
As you can see here, the client received no input at all until I sent the
"date\n" command. The server ran the "date" command, and sent the results
back to the client. The client recenved the date and time, preceded by
the two "Command? " prompts that it hadn't gotten earlier.
As you can see, I'm familiar with $| and the uses of autoflush. According
to the FAQs, any one of these should suffice to unblock the buffering. But
the data going from TCPserver to TCPclient is bufferred until a newline is
sent.
Is there any way to make the messaging work here?
getting them to do some simple request-response sequences. A bizarre flushing
failure has popped up in all of them, and no amount of futzing with $| and
autoflush seems to make them work.
Here's the code for one of the simplest pairs.
=====================================
The TCPserver.pl program is:
#!/usr/local/bin/perl -w
use IO::Socket;
use Net::hostent;
$port = 4217; # pick something not in use
select STDOUT; $| = 1;
($P = $0) =~ s".*/"";
$V = $ENV{"V_$P"} || 2; # Verbose level
$prompt = "Command? ";
$EOL = "\015\012"; # Paranoia
$server = IO::Socket::INET->new( Proto => 'tcp',
LocalPort => $port,
Listen => SOMAXCONN,
Reuse => 1);
die "can't setup server ($!)" unless $server;
print "[Server $0 accepting clients on port $port]$EOL";
while ($client = $server->accept()) {
$client->autoflush(1);
select $client; $| = 1; select STDOUT;
print $client "Welcome to $0; type help for command list.$EOL";
$hostinfo = gethostbyaddr($client->peeraddr);
printf "[Connect from %s]$EOL", $hostinfo->name || $client->peerhost;
select STDOUT;
print "SEND \"$prompt\"$EOL";
print $client $prompt;
print "SENT \"$prompt\"$EOL";
while ($line = <$client>) {
print "RCVD \"$line\"$EOL" if $V>1;
$line =~ s/[\r\n]+$//;
next unless $line; # blank line
# autoflush $client 1; # Does this help? Nope
if ($line =~ /quit|exit/i) { last; }
elsif ($line =~ /date|time/i) { printf $client "%s$EOL", scalar localtime; }
elsif ($line =~ /who/i ) { print $client `who 2>&1`; }
elsif ($line =~ /cookie/i ) { print $client `/usr/games/fortune 2>&1`; }
elsif ($line =~ /motd/i ) { print $client `cat /etc/motd 2>&1`; }
else {
print $client "Commands: quit date who cookie motd$EOL";
}
} continue {
select STDOUT;
print "SEND \"$prompt\"$EOL";
print $client $prompt;
print "SENT \"$prompt\"$EOL";
}
close $client;
}
==============================
And here's the TCPclient.pl program:
#!/usr/local/bin/perl -w
use strict;
use IO::Socket;
my ($host, $port, $kidpid, $server, $line);
my $EOL = "\015\012"; # Paranoia
select STDOUT; $| = 1;
my $P = $0; $P =~ s".*/"";
my $V = $ENV{"V_$P"} || 2; # Verbose level
if (@ARGV <1) {push @ARGV, 'localhost'} # Default to local server
if (@ARGV <2) {push @ARGV, '4217'} # Default port for TCPserver.pl
($host, $port) = @ARGV;
# create a tcp connection to the specified host and port
$server = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
or die "can't connect to port $port on $host: $!";
$server->autoflush(1); # so output gets there right away
#autoflush $server 1;
select $server; $| = 1; select STDOUT;
print "[Connected to $host:$port]$EOL";
# split the program into two processes, identical twins
die "can't fork: $!" unless defined($kidpid = fork());
# the if{} block runs only in the parent process
if ($kidpid) { # copy the socket to standard output
print "READ ...$EOL" if $V>1;
while (defined ($line = <$server>)) {
print "RCVD \"$line\"$EOL" if $V>1;
print STDOUT $line;
}
kill("TERM", $kidpid); # send SIGTERM to child
} else { # the else{} block runs only in the child process
# copy standard input to the socket
while (defined ($line = <STDIN>)) {
print "SEND \"$line\"$EOL" if $V>1;
print $server $line;
print "SENT \"$line\"$EOL" if $V>1;
}
}
=====================================
Some readers may recognize these from online sources. Anyway, the Server's
"Command? " prompt is sent to the client, but the client doesn't receive it
at all until the server sends something that ends with a newline (which was
coded "\n" in an earlier version, and "\015" here as a variant). This despite
the setting of $| to 1 for every file in sight, and the use of autoflush(1)
for the sockets also. I tried the autoflush function, too, though it's
commented out here. None of these attempts to subvert the buffering works;
the server's prompt requires a newline for it to be read by the client.
For example, I started the two programs up in two windows, they both printed
their startup messages, the server produced a "SEND ..." and "SENT ..." message
for the prompt, and both were hung. In the client window, I hit Enter, and then
types a "date" command plus an Enter. On the server side, the output was:
=====================================
: ./TCPserver.pl
[Server ./TCPserver.pl accepting clients on port 4217]
[Connect from localhost]
SEND "Command? "
SENT "Command? "
RCVD "
"
SEND "Command? "
SENT "Command? "
RCVD "date
"
SEND "Command? "
SENT "Command? "
=====================================
That looks like exactly what you'd expect.
Meanwhile, over on the client side, what's on the screen is:
=====================================
: ./TCPclient.pl
[Connected to localhost:4217]
READ ...
RCVD "Welcome to ./TCPserver.pl; type help for command list.
"
Welcome to ./TCPserver.pl; type help for command list.
[Here I hit the Enter key]
SEND "
"
SENT "
"
date [Here I sent an actual command]
SEND "date
"
SENT "date
"
RCVD "Command? Command? Wed Jan 21 17:17:03 2004 <=== The prompts finally appear!!
"
Command? Command? Wed Jan 21 17:17:03 2004
=====================================
As you can see here, the client received no input at all until I sent the
"date\n" command. The server ran the "date" command, and sent the results
back to the client. The client recenved the date and time, preceded by
the two "Command? " prompts that it hadn't gotten earlier.
As you can see, I'm familiar with $| and the uses of autoflush. According
to the FAQs, any one of these should suffice to unblock the buffering. But
the data going from TCPserver to TCPclient is bufferred until a newline is
sent.
Is there any way to make the messaging work here?