home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


C.3 An Interactive Client

It's pretty easy to make a client that just reads everything from a server, or that sends one command, gets one answer, and quits. But what about setting up something fully interactive, like telnet ? That way you can type a line, get the answer, type a line, get the answer, and so on. (OK, usually telnet operates in character mode, not line mode, but you get the idea.)

This client is more complicated than the two we've done so far, but if you're on a system that supports the powerful fork call, the solution isn't that rough. Once you've made the connection to whatever service you'd like to chat with, call fork to clone your process. Each of these two identical processes has a very simple job to do: the parent copies everything from the socket to standard output, while the child simultaneously copies everything from standard input to the socket. To accomplish the same thing using just one process would be much harder, because it's easier to code two processes to do one thing than it is to code one process to do two things.[ 5 ]

[5] This keep-it-simple principle is one of the cornerstones of the UNIX philosophy, and good software engineering as well, which is probably why it's spread to other systems as well.

Here's the code:

#!/usr/bin/perl -w
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());
# the if{} block runs only in the parent process
if ($kidpid) { 
    # copy the socket to standard output
    while (defined ($line = <$handle>)) {
    print STDOUT $line;
    }
    kill("TERM", $kidpid); # send SIGTERM to child
}
# the else{} block runs only in the child process
else { 
    # copy standard input to the socket
    while (defined ($line = <STDIN>)) {
    print $handle $line;
    }
}

The kill function in the parent's if block is there to send a signal to our child process (current running in the else block) as soon as the remote server has closed its end of the connection.