[Techtalk] Perl Sockets Communication

Almut Behrens almut_behrens at yahoo.com
Tue Aug 21 23:41:55 EST 2001


On Mon, Aug 20, 2001 at 03:37:05PM -0700, Kai MacTane wrote:
> I'm trying to write a pair of Perl programs, one to run as a CGI script 
> from a Web server, and the other to run as a daemon process. I'd like them 
> to communicate via a local Unix socket. I'm essentially following the 
> recipe in the Camel Book, page 353. From the logging I've been able to 
> perform, it seems that the client script connects to the daemon, but no 
> information is passed through the socket.

Hi,

without having seen your exact code, I can only guess what the
problem might be: it could have to do with some buffering issue
(block-buffered vs. line-buffered, etc.), or maybe just some other
tiny glitch -- often it's only a few characters that keep a program
from doing what it's supposed to be doing...
If you're interested in why your specific piece of code doesn't work,
just post it here, and I'm sure someone will see what's wrong :)

If OTOH, you just want some working example to build up upon, you
might want to have a look at the following minimal implementations
of a daemon and client using unix domain sockets. I've deliberately
kept them without any bells and whistles (well, almost -- assigning
to $0 to get a nice info in 'ps' can be considered "bells"...), so
the essential elements are seen more clearly -- and it's of course
far from production-quality code... (error handling, security)

The daemon is written in the typical style, forking off a subprocess
to handle each connection.  It reads stuff from the client until it
receives an empty line, which causes it to close the connection. It
operates individually on every line read (some useful code would have
to be added, of course).

The client also forks two seperate processes to prevent potential
deadlock situations which could occur otherwise, if the socket buffers
of the OS get filled up in either direction. One process writes to the
daemon's socket, the other reads the daemon's response, independently.
If you only exchange individual short messages (less than a few kB),
this would typically not be required. But keep in mind that deadlocks
are something to avoid at any price, as they'll cause your program to
hang indefinitely.
(A deadlock occurs, if there's only one process which doesn't handle
writing and reading (pseudo-)simultaneously. Let's say you write
something to the socket. The daemon responds, and the data gets
temporarily buffered by the system. If your client doesn't read/empty
the buffer before it gets filled up (because it still keeps on writing
more stuff instead, for example), it'll happen eventually that the
daemon can no longer write its response, and, because it has to wait,
it no longer reads further requests from the client. This will lead to
the other buffer getting filled up, until the client can no longer
write, too. Then, both are waiting for the other side to do something.
Not a good thing...)
There are many ways to prevent such deadlock situations, the "seperate
processes" version is just a very simple one. The select() function
can also be quite useful here, for example.

Also, there are many different ways to write daemons: forking or
pre-forking types, ones using select() or any combination thereof.
See the "Perl Cookbook" (O'Reilly) for more examples (IMHO better
than those from the Camel book). Also, I've written quite a couple of
perl daemons myself, so I'm sure we'll find the combination of perl
statements that will suit your needs (in case you should need more
assistance ;)

Here's the code, making use of the (OO-style) module "IO::Socket".
(Similar effects could of course be achieved using perl's lowlevel
socket functions...)

Final tip: you mentioned that the client is going to be a CGI program.
Just in case you need a performance boost here, consider using FastCGI.
Especially when using the Apache server, there's a mod_fastcgi module
and the associated perl-side FCGI.pm module (more info at
http://www.fastcgi.com).
I've made very good experiences with this setup. It typically works out
of the box, and is reasonably simple to configure. Also, it's typically
less of a memory hog than the fullblown solutions like mod_perl,
mod_python, etc. where the whole interpreter is integrated into the
webserver itself (those have other advantages, of course, but this is
another issue... ;)

Have fun!

- Almut


--- the daemon ---

#!/usr/bin/perl

use IO::Socket;
use POSIX ":sys_wait_h";   # (for WNOHANG)

my $sockname = "/tmp/unixsockd.sock";

start_daemon();

sub start_daemon {
    my $pid;
    if ($pid = fork()) {
        waitpid($pid, 0);
    } else {
        if ($pid = fork()) { exit; }
        $0 = "unixsockd: accepting connections on $sockname";  # for ´ps´
        service_clients( get_sock() );       # wait for incoming requests
    }
}

sub get_sock {
    unlink $sockname;
    my $sock = IO::Socket::UNIX->new(
                   Local  => $sockname,
                   Type   => SOCK_STREAM,
                   Listen => SOMAXCONN,
               ) or die "$0: error starting daemon on '$sockname': $@\n";
    # you might want to change permissions and ownership, e.g.:
    #chmod 0600, $sockname;
    #chown scalar getpwnam('nobody'), 0, $sockname;
    return $sock;
}

sub service_clients {
    my $sock = shift;
    $SIG{CHLD} = \&reaper;
    
    my $client;
    while ( $client = $sock->accept() ) {
        my $pid = fork();  die "Cannot fork\n" unless defined $pid;
        if ($pid) {                   # parent
            close $client;            # no use to parent
            next;                     # be ready for another client
        }
        # child
        close $sock;                  # no use to child
        process_requests($client);
        exit;                         # terminate child
    }
}

sub process_requests {
    my $client = shift;
    $0 = "unixsockd: handling requests...";  # for ´ps´
    while ( my $line = <$client> ) {  # read line from socket
        last if $line =~ /^\s$/;      # exit on empty line
        chomp $line;
        # put some more useful code here...
        printf $client "%s: %s, handled by PID %d\n",
                       scalar localtime(time), $line, $$;
                                      # return something to client
    }
}

sub reaper { 
    while (waitpid(-1,WNOHANG) > 0) {}
    $SIG{CHLD} = \&reaper; 
}


--- the client ---

#!/usr/bin/perl

use IO::Socket;

my $sockname = "/tmp/unixsockd.sock";

my $client = IO::Socket::UNIX->new(
                 Peer    => $sockname,
                 Type    => SOCK_STREAM,
                 Timeout => 5,
             ) or die "$0: error connecting to '$sockname': $@\n";

my $pid = fork();  die "Cannot fork\n" unless defined $pid;

if ($pid) {
    write_sock();
    waitpid($pid, 0);
} else {
    read_sock();
}

sub write_sock {
    for (1..1000) {            # (feel free to change count)
        print $client "testline number $_\n";    # print to socket
    }
    print $client "\n";        # empty line causes server
                               # to terminate connection
    #print "Done writing.\n";  # (goes to stdout, not socket)
}

sub read_sock {
    while (my $line = <$client>) {
        print $line;           # report to stdout
        # simulate someone reading slooowly (50ms/line):
        #select(undef, undef, undef, 0.05);
    }
}

---

Note #1:
Terminate the daemon using "kill <PID>". Simply starting it anew will
overwrite the socket file, but won't kill the daemon process...

Note #2:
Do a "ps xf" (from a different terminal) while the programs interact.
You should see the structure of the multiple processes in action...

Note #3:
Uncomment the 'select(...0.05)' and the 'print "Done writing.\n"' in
the client code, and watch closely what gets printed to stdout in what
sequence, and the timestamps of the daemon's responses. This will give
you an idea of the bufferering that's happening...





More information about the Techtalk mailing list