You want set up a fully interactive client so you can type a line, get the answer, type a line, get the answer, etc., somewhat like telnet.
Once you've connected, fork off a duplicate process. One twin only reads your input and passes it on to the server, and the other only reads the server's output and sends it to your own output.
In a client-server relationship, it is difficult to know whose turn it is to talk. Single-threaded solutions involving the four-argument version of select
are hard to write and maintain. But there's no reason to ignore multitasking solutions. The fork
function dramatically simplifies this problem.
Once you've connected to the service you'd like to chat with, call fork
to clone a twin. Each of these two (nearly) identical processes has a simple job. The parent copies everything from the socket to standard output, and the child simultaneously copies everything from standard input to the socket.
The code is in Example 17.4.
#!/usr/bin/perl -w # biclient - bidirectional forking client use strict; use IO::Socket; my ($host, $port, $kidpid, $handle, $line); unless (@ARGV == 2) { die "usage: $0 host port" } ($host, $port) = @ARGV; # create a tcp connection to the specified host and port $handle = IO::Socket::INET->new(Proto => "tcp", PeerAddr => $host, PeerPort => $port) or die "can't connect to port $port on $host: $!"; $handle->autoflush(1); # so output gets there right away print STDERR "[Connected to $host:$port]\n"; # split the program into two processes, identical twins die "can't fork: $!" unless defined($kidpid = fork()); if ($kidpid) { # parent copies the socket to standard output while (defined ($line = <$handle>)) { print STDOUT $line; } kill("TERM" => $kidpid); # send SIGTERM to child } else { # child copies standard input to the socket while (defined ($line = <STDIN>)) { print $handle $line; } } exit;
To accomplish the same thing using just one process is remarkably more difficult. It's easier to code two processes, each doing a single task, than it is to code one process to do two different tasks. Take advantage of multitasking by splitting your program into multiple threads of control, and some of your bewildering problems will become much easier.
The kill
function in the parent's if
block is there to send a signal to the child (currently running in the else
block) as soon as the remote server has closed its end of the connection. The kill
at the end of the parent's block is there to eliminate the child process as soon as the server on the other end goes away.
If the remote server sends data a byte at time and you need that data immediately without waiting for a newline (which may never arrive), you may wish to replace the while
loop in the parent with the following:
my $byte; while (sysread($handle, $byte, 1) == 1) { print STDOUT $byte; }
Making a system call for each byte you want to read is not very efficient (to put it mildly), but it is the simplest to explain and works reasonably well.
The
sysread
and
fork
functions in Chapter 3 of Programming Perl and in perlfunc (1); the documentation for the standard IO::Socket module; Recipe 16.5; Recipe 16.10; Recipe 17.11