jalewis2 has asked for the wisdom of the Perl Monks concerning the following question:

I have a forking tcp server that allows clients to connect and query my datasets. I have multiple datasets and would like to run a server for each dataset on a different port. I built my server based on the Perl Cookbook Pre-forking server and it works as expected.

My problem is when I want to start multiple servers from the same script with each server using options from an array to determine what dataset is used. Essentially, each loop passes the name of the DB for that dataset.

My first attempt was to loop through my datasource variables and pass them to the forking server, but the loop pauses on the first server until I ctrl-c.

I was hoping a fellow monk could point me in the right direction for starting multiple forking servers from the same script.

Replies are listed 'Best First'.
Re: Opening multiple forking servers
by Rhandom (Curate) on Mar 06, 2007 at 16:17 UTC
    I'm not sure if you can make it more simple than the following:

    package Foo; use base qw(Net::Server::Fork); my @ports = qw(20203 20204 20205); my @dbs = qw(tom jim jane); my %p_map; @p_map{@ports} = @dbs; Foo->run(port => \@ports); sub process_request { my $self = shift; my $port = $self->get_property('sockport'); my $db = $p_map{$port}; print "Welcome! You connected on port $port - your db is $db\n"; # run default echo server $self->SUPER::process_request(@_); }

    If you are really opposed to Net::Server you could always read through its guts to see what it is doing and roll your own. Or keep your life simple. Enjoy!

    Update: I guess really though this example is cheating - I haven't started three servers. I've only started one server that is listening on three ports. But I think it would be much much easier to maintain.

    Update 2: Looking at your code you want a PreFork server. Well then you'd need to do the following:
    use base qw(Net::Server::PreForkSimple); Foo->run( max_servers => 5, # from jwlewis sample code max_requests => 5, port => \@ports, );

    my @a=qw(random brilliant braindead); print $a[rand(@a)];
      Thanks for the post, I think this is what I was really after. I should be able to fit my server into the Net::Server framework.
Re: Opening multiple forking servers
by Moron (Curate) on Mar 06, 2007 at 15:44 UTC
    I think we'll need to see the code that leads up to the pausing.

    -M

    Free your mind

      yeah, I figured someone would want to see code right after I posted. This is the wrapper to start servers:
      #!/usr/bin/perl use strict; my $path = "~/bin"; my $port = 4000; my $cmd; my @datasources = qw(db1 db2 db3 db4); foreach my $datasource (@datasources) { $cmd = "$path/prefork_server.pl $datasource $port"; print "$cmd\n"; system($cmd); $port++; }
      The prefork server from the cookbook which is prefork_server.pl in the above code:
      #!/usr/bin/perl # preforker - server who forks first use IO::Socket; use Symbol; use POSIX; my $datasource = $ARGV[0]; my $port = $ARGV[1]; # establish SERVER socket, bind and listen. $server = IO::Socket::INET->new(LocalPort => $port, Type => SOCK_STREAM, Proto => 'tcp', Reuse => 1, Listen => 10 ) or die "making socket: $@\n"; # global variables $PREFORK = 5; # number of children to maintain $MAX_CLIENTS_PER_CHILD = 5; # number of clients each child sho +uld process %children = (); # keys are current child process I +Ds $children = 0; # current number of children sub REAPER { # takes care of dead children $SIG{CHLD} = \&REAPER; my $pid = wait; $children --; delete $children{$pid}; } sub HUNTSMAN { # signal handler for SIGINT local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children kill 'INT' => keys %children; exit; # clean up with dignity } # Fork off our children. for (1 .. $PREFORK) { make_new_child(); } # Install signal handlers. $SIG{CHLD} = \&REAPER; $SIG{INT} = \&HUNTSMAN; # And maintain the population. while (1) { sleep; # wait for a signal (i.e., child's + death) for ($i = $children; $i < $PREFORK; $i++) { make_new_child(); # top up the child pool } } sub make_new_child { my $pid; my $sigset; # block signal for fork $sigset = POSIX::SigSet->new(SIGINT); sigprocmask(SIG_BLOCK, $sigset) or die "Can't block SIGINT for fork: $!\n"; die "fork: $!" unless defined ($pid = fork); if ($pid) { # Parent records the child's birth and returns. sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock SIGINT for fork: $!\n"; $children{$pid} = 1; $children++; return; } else { # Child can *not* return from this subroutine. $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did be +fore # unblock signals sigprocmask(SIG_UNBLOCK, $sigset) or die "Can't unblock SIGINT for fork: $!\n"; # handle connections until we've reached $MAX_CLIENTS_PER_CHIL +D for ($i=0; $i < $MAX_CLIENTS_PER_CHILD; $i++) { $client = $server->accept() or last; # do something with the connection } # tidy up gracefully and finish # this exit is VERY important, otherwise the child will become # a producer of more and more children, forking yourself into # process death. exit; } }

        Two things.

        1. I think you mean for your system call to be inside the foreach loop.
        2. Your prefork_server.pl never exits. It sits in the foreground and manages the children doing the work.

        A simple way to get many servers would be to add '&' to the end of your $cmd. That way, the shell will fork and execute prefork_server.pl and return immediately (so you can then spawn the next prefork_server.pl).