Writing Bidirectional Clients (Perl Cookbook, 2nd Edition)

## 17.10. Writing Bidirectional Clients

### 17.10.1. Problem

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.

### 17.10.2. Solution

Once you've connected, fork off a duplicate process. One twin reads only your input and passes it on to the server, and the other reads only the server's output and sends it to your own output.

### 17.10.3. Discussion

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.

#### Example 17-4. biclient

>       #!/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.

### 17.10.4. See Also

The `sysread` and `fork` functions in Chapter 29 of Programming Perl and in perlfunc(1); the documentation for the standard IO::Socket module; Recipe 16.5; Recipe 16.10; Recipe 17.11

Copyright © 2003 O'Reilly & Associates. All rights reserved.