Book HomeMastering Perl/TkSearch this book

Chapter 19. Interprocess Communication with Pipes and Sockets

Contents:

Handling Unsolicited Media Changes
IPADM Design Considerations
The Perl/Tk IPADM Client, ipadm
The IPADM Helper, ipadmh
The IPADM Daemon, ipadmd
Polling Win32 Sockets

The term interprocess communication describes a scenario in which two or more processes talk to one another. The processes may reside on the same computer or they may be on separate computers connected via a network. In Unix, there are several IPC mechanisms: pipes, semaphores, signals, shared memory, and sockets, to name a few. All are useful for local communication, but sockets are typically used for communication over a network.

The problem with reading and writing a network socket is that an I/O operation often takes a considerable amount of time to complete, which, as described in Chapter 15, "Anatomy of the MainLoop", might block the flow of Tk events. Your program then stalls, and the user becomes unhappy. But with care, it's possible to mix network programming with Perl/Tk and still maintain lively event processing.

In this chapter, we'll write some Perl/Tk network programs using pipes and sockets. In Chapter 20, "IPC with send", we'll cover more examples using an IPC mechanism unique to Tk, the send command.

The first example in this chapter is a simple TCP/IP media change client/server, just to become familiar with the basic mechanisms. These programs help automate daily tape backups on a Linux computer. The client sends an operator message to a central machine, requesting a tape change, and the server posts the message in a Perl/Tk window and returns the operator's response.

Later, we'll develop a Tk user interface that updates flat files on a remote machine, but won't freeze in the face of I/O delays and timeouts due to network latency or disabled servers. The model is extremely simple; it depends on fileevent,[48] in conjunction with a helper process that uses Unix pipes to communicate with the Perl/Tk client, and TCP/IP sockets to communicate with the remote daemon. Information is exchanged between the client and daemon using a command/response protocol mediated by the helper. The client pipes a command to the helper, which forwards it to the daemon. The daemon obliges by sending output (and errors) to the helper for piping to the Tk client.

[48] Although there have been success stories, fileevent on Win32 operating systems is at best problematic. We present two possible workarounds: polling and shared memory. The former solution appears in another section of this chapter, while the latter is written-up in Chapter 22, "Perl/Tk and the Web". A newer Perl, combined with the upcoming Tk 8.3, should resolve these deficiencies.

19.1. Handling Unsolicited Media Changes

A good system administrator always implements backup procedures, which protect her machines from damaged disks, malevolent miscreants, or rash <Return> s. The venerable tape is still the backup medium of choice for most shops due to its low cost and high reliability. Typically, a full system backup is performed, say, once a week, followed by periodic incremental change dumps. It's not uncommon for a full dump to require several tape volumes, and some sort of operator intervention is required to swap tapes.

But our administrator, in accordance with Perl's First Virtue, Laziness, is also likely to initiate these backups via cron or some similar automated means, which means there is no terminal for the backup program to communicate with her. Fortunately, the backup program has an option to run a user-specified program when a media change is required. This alert program is typically a script to send a mail message or display a window that attracts the operator's attention. Sending mail seems to lack style. But opening a window is cool unless there are many machines, each with its own window, so let's write a TCP/IP server that displays media change messages in Tk windows on a single machine, as they arrive from any number of backup client machines.

19.1.1. The Media Change Client, mediachangec

When the system backup reaches end of volume, it executes this incredibly simple client code, specifying the IP address of the media change server plus a message string to display. The code first opens a socket to the server and unbuffers it.[49]

[49] The autoflush call is not required in recent versions of IO::Socket but is included for completeness.

The PeerAddr and PeerPort options specify the host to contact and the TCP/IP port the server listens on. The client code then outputs the message on the socket, reads the operator's response from the socket, and prints it on STDOUT.

use IO::Socket;

do {print "Usage:  mediachangec host message\n"; exit} unless @ARGV == 2;

$sock = IO::Socket::INET->new(
    PeerAddr => $ARGV[0],
    Proto    => 'tcp',
    PeerPort => 8979,
);
die "Cannot connect: $!" unless defined $sock;
$sock->autoflush(1);

print $sock $ARGV[1], "\n";     # send operator message
print STDOUT <$sock>;           # display operator response

As shown in Figure 19-1, the response comes directly from a messageBox widget posted on the server's display and can be either "Ok" or "Cancel."

Figure 19-1

Figure 19-1. A media change operator message

19.1.2. The Media Change Server, mediachanged

We're about to see a forking TCP/IP server, sometimes called a network daemon. In typical Unix fashion, we'll call it mediachanged, with the trailing d identifying it as a daemon program. This bare-bones example serves as the basis for the much more complicated IPADM server discussed in the remaining sections of this chapter.

The following code is initiated on the media change server at boot time and runs forever. It opens a listen socket on the little-known port 8979 and, as requests arrive, it forks a new child that creates a Tk MainWindow with a lone messageBox widget. Here are the preamble statements:

#!/usr/local/bin/perl -w
# 
# mediachanged - media change daemon.
#
# Wait for connect on our well known port, display the received message
# in a Tk messageBox, and return the operator's reply.

use 5.005;
use IO::Handle;
use IO::Socket;
use Net::hostent;
use POSIX qw/setsid/;
use Tk;
use subs qw/client_connect/;
use strict;

This statement ensures that the daemon's children are properly disposed of after they terminate:

$SIG{CHLD} = sub {wait};

The program backgrounds itself by forking and terminating the parent:

my $pid = fork;
die "fork failed: $!" unless defined $pid;
exit if $pid;                   # parent exits, daemon continues
setsid or die "setsid failed: $!";

Here we create a listen socket, distinct from the client's connect socket, differentiated by the Listen option that specifies how many simultaneous incoming connections on port 8979 are possible:

my $server = IO::Socket::INET->new(
    Proto     => 'tcp',
    LocalPort => 8979,
    Listen    => SOMAXCONN,
    Reuse     => 1,
);

die "Socket::INET::new failed: $!" unless defined $server;

At last, here is the daemon's main loop, where it blocks on the accept statement until a media change request arrives. It then makes a (very) feeble attempt at authentication by verifying that the peer's IP address is from a legal subnet (192.168.x.x). Unfortunately, an IP address is easily spoofed, so this authentication scheme is unreliable. Nevertheless, within the context of an isolated machine room, the data is useful enough, so we initialize the variable $from with the peer's human-readable IP name.

The daemon then quickly spawns a child to handle the request and resumes waiting on the accept. The child unbuffers the network socket, $ns, and calls client_connect to continue processing.

while (my $ns = $server->accept) {

    my $peeraddr = $ns->peeraddr;
    my $hostinfo = gethostbyaddr($peeraddr);
    my $remote_host = $hostinfo->name || $ns->peerhost;
    my(@inetaddr) = unpack('C4', $peeraddr);
    my $from = "Message from $remote_host (" . join('.', @inetaddr) . ')';
    unless ($inetaddr[0] == 192 and $inetaddr[1] == 168) {
        close $ns;
        next;
    }
 
    if (my $pid = fork) {
        close $ns or die "Client socket close failed: $!";
    } elsif (defined $pid) {
        $ns->autoflush(1);
        client_connect $ns, $from;
    } else {
        die "fork error: $!";
    }
    
} # whilend forever network daemon

Subroutine client_connect first reads the media change message from the network socket. Then it creates the MainWindow, withdraws it, creates a 15-second repeating timer event to ring the bell and alert the operator, and posts an OKCancel messageBox dialog widget. Eventually the operator interacts with the messageBox, which returns either the string OK or the string Cancel. This reply string is sent to the peer via the network socket, and we're done.

sub client_connect {

    # Process a client connect - send our client either an "Ok" or
    # "Cancel" string, depending upon how the media change went.

    my($ns, $from) = @_;

    chomp( $_ = <$ns> );

    my $mw = MainWindow->new;
    $mw->withdraw;
    $mw->bell;
    $mw->repeat(15 * 1000 => sub {$mw->bell});
    my $reply = $mw->messageBox(
        -icon       => 'info',
        -type       => 'OKCancel',
        -message    => "$from\n\n$_",
        -wraplength => '6i',
        -title      => 'mediachanged',
        -background => '#ECFFFF',
    );
    print $ns "$reply\n";
    close $ns;
    exit;

} # end client_connect

Armed with the know-how to build a TCP/IP client/server, let's examine a more complicated scenario involving a Perl/Tk client.



Library Navigation Links

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