Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

A suicidal parent OR death of a forking server

by MonkeyMonk (Sexton)
on Feb 09, 2010 at 12:44 UTC ( [id://822182]=perlquestion: print w/replies, xml ) Need Help??

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

I seek the knowledge of the enlightened monks to help me reach the last leg of a forking server.

I have tried to read documentation available online, in cookbooks but I am not sure if it is the same as what I want.

The aim is to:

    1. Create a telnet server running on port 7070.
    2. Enable 5 clients at MAX to connect to it.
    3. When 1 client is connected and running a command, all commands from other clients should be queued.(There are only 5 possible commands that a client can invoke, 2 of which are "printhelp" and "quit")

I have managed to reach level 1 and partially level 2. Multiple clients are able to connect to the server.

The problems that I face are these

    A) When the first client dies, the server commits suicide. Advise is sought on this.
    B) How can I prevent more than 5 clients from connecting.
    C) How can I enable queuing of commands and ensuring the result of the commands are sent to the right client.
#!/usr/bin/perl -w use IO::Socket; use strict; $SIG{CHLD} = sub{ wait; }; my $inputLine; my $new_sock; my $main_sock; my $pid; # Create Socket on port 7070 $main_sock = new IO::Socket::INET ( LocalHost => 'localhost', LocalPort => '7070', Proto => 'tcp', Listen => 1, Reuse => 1, ); die "Could not create socket: $!\n" unless $main_sock; print "Telnet Server listening on 7070....\n"; # Accept connection and fork child! while( $new_sock = $main_sock->accept() ){ $pid = fork(); unless( defined($pid) ){ die "Cannot fork\n"; } ################## # CHILD PROCESS ################## if($pid == 0) { # Print Welcome message! welcomeClient($new_sock); while( defined( $inputLine=<$new_sock>) ) { $inputLine =~ s/[\r\n]//g; if( $inputLine eq "printhelp" ){ callPrintHelp($new_sock); }elsif( $inputLine eq "quit" ){ callQuit($new_sock); exit(0); }else{ print $new_sock "INFO> Not implemented yet +\n"; } }#while exit(0); } ################## # CHILD PROCESS ################## }# while main_sock close($main_sock); sub welcomeClient { # Show starting point for client and help messages my ($client_sock) = @_; print $client_sock "######################################\n"; print $client_sock " Telnet Interface \n"; print $client_sock "######################################\n\n"; print $client_sock "INFO> Type 'printhelp' for help\n\n"; print $client_sock "READY:\n"; print $client_sock ">"; } sub callPrintHelp{ my ($client_sock) = @_; ...... ..... } sub callQuit{ my ($client_sock) = @_; ...... ..... }

Replies are listed 'Best First'.
Re: A suicidal parent OR death of a forking server
by ikegami (Patriarch) on Feb 09, 2010 at 16:21 UTC
    You don't have any synchronicity problem if you don't fork. So don't fork. use IO::Select instead.
    #!/usr/bin/perl use strict; use warnings; use IO::Socket::INET qw( ); use IO::Select qw( ); sub process_msg { my ($client, $msg) = @_; chomp $msg; my $host = $client->peerhost; print "$host said '$msg'\n"; return lc($msg) ne 'quit'; } my $server = IO::Socket::INET->new( ... ) or die("Couldn't create server socket: $!\n"); my $select = IO::Select->new($server); my %bufs; while (my @ready = $select->can_read) { for my $fh (@ready) { if ($fh == $server) { my $client = $server->accept; $select->add($client); $bufs{fileno($client)} = ''; my $host = $client->peerhost; print "[Accepted connection from $host]\n"; } else { our $buf; local *buf = \$bufs{fileno($fh)}; my $rv = sysread($fh, $buf, 64*1024, length($buf)); if (!$rv) { my $host = $fh->peerhost; if (defined($rv)) { print "[Error reading from host $host]\n"; } else { print "[Connection from $host terminated]\n"; } process_msg($fh, $buf) if length($buf); delete $bufs{fileno($fh)}; $sel->remove($fh); next; } while ($buf =~ s/\G(.*\n)//) { if (!process_msg($fh, "$1")) { my $host = $fh->peerhost; print "[Connection from $host terminated]\n"; delete $bufs{fileno($fh)}; $sel->remove($fh); last; } } } } }

      Instead of implementing a limited cooperative multitasking system using select, it's simpler to use threads or Coro.

      The following is the Coro equivalent of the code in the parent post.

      #!/usr/bin/perl use strict; use warnings; use Coro qw( async ); use IO::Socket::INET qw( ); sub client { my ($sock) = @_; my $host = $client->peerhost; print "[Accepted connection from $host]\n"; while (!eof($sock)) { my $msg = <$sock>; if (!defined($msg)) { print "[Error reading from host $host]\n"; return; } print "$host said '$msg'\n"; last if $msg eq 'quit'; } print "[Connection from $host terminated]\n"; } my $server = IO::Socket::INET->new( ... ) or die("Couldn't create server socket: $!\n"); async(\&client, $server->accept) while 1;
        Instead of implementing a limited cooperative multitasking system using select, it's simpler to use ... Coro.

        But what you've posted here is just as limited as the select version (that you also posted). It's not multi-tasking, Just cooperative time-sharing.

        It suffers from all the same limitations and caveats as the select implementation, except that they are stuffed under the cover of a "pretty interface".

        Limitations that include:

        • The inability to scale across multiple cores.
        • The need to break up cpu-intensive processing into "bite-sized chunks".

        Yesterdays years decades solution masquerading as something new, that headlines with what is both a factual and technical lie.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: A suicidal parent OR death of a forking server
by SuicideJunkie (Vicar) on Feb 09, 2010 at 14:03 UTC

    1) Don't have the child die. Simply have it return instead.

    2)a) Keep a count and refuse any more connections.
    b) Have exactly 5 children at all times, that don't return but merely go idle when their client disconnects. Have the parent check for any available idle children when a new connection comes in, and assign the connection to one of them. Refuse if no children are idle.

    3)a) Use a semaphore.
    b) Push onto a thread safe array of commands to do from the children, and shift off the action to do next in a work thread.
    c) Token passing between the children.
    d) Etc.

Re: A suicidal parent OR death of a forking server
by roboticus (Chancellor) on Feb 09, 2010 at 14:09 UTC

    MonkeyMonk:

    Regarding (B): Since you make the connection using the accept method on your socket, all you need to do is keep track of the number of connections you have. Once you have five connections, simply stop accepting connections until one of the children disconnect.

    ...roboticus

Re: A suicidal parent OR death of a forking server (Use threads)
by BrowserUk (Patriarch) on Feb 09, 2010 at 19:23 UTC

    'Tis easy with threads:

    #! perl -slw use strict; use threads; use threads::shared; use IO::Socket; use constant CRLF => chr( 13 ) . chr( 10 ); my $server = IO::Socket::INET->new( LocalHost => 'localhost', LocalPort => '7070', Proto => 'tcp', Listen => 5, Reuse => 1, ) or die "Couldn't create listening socket"; my $running :shared = 0; $/ = $\ = CRLF; while( 1 ) { my $client = $server->accept; print "running: $running"; close( $client ), next if $running >= 5; print "Accepted connection from $client"; async { { lock $running; ++$running;} print "$client running"; while( ( my $input = <$client> ) ne 'quit' . CRLF ) { chomp $input; if( $input eq 'printhelp' ) { print $client 'Some help' . CRLF; } elsif( $input eq 'cmd1' ) { print $client 'CMD1 stuff' . CRLF; } elsif( $input eq 'cmd2' ) { print $client 'CMD2 stuff' . CRLF; } elsif( $input eq 'cmd3' ) { print $client 'CMD3 stuff' . CRLF; } else { print $client "Unrecognised command; '$input'" . CRLF; } } lock $running; --$running; print "$client done"; }->detach; } close $server;

    That really is the complete program to satisfy your specs. Though you might want to add a signal handler to clean up a little when interupted.

    It easier than fork because you can use shared memory to count how many concurrent clients you have without getting into the complexities of IPC.

    It's better than select because: a) you don't have to get into writing your own line handling; b) if one of those commands takes 1/2 hour to complete, the whole server doesn't grind to a halt.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      That is the cleanest example IO::Socket::INET server I have seen yet. Nicely done.

        Thanks. It does have limitations thought. Specifically, it will only handle low 10s of concurrent connections before memory becomes an issue. And even if you keep within those levels of concurrency, if the connections are short-lived and made at high speed, it will be CPU-expensive.

        But for the OPs low concurrency, long-lived connections application, it is remarkably simple and stable.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

      Many thanks! Please see my reply to the original post.

      I think I will have to eventually look at what you have written. I am yet to fully explore the drawbacks of the forking.

Re: A suicidal parent OR death of a forking server
by cdarke (Prior) on Feb 09, 2010 at 15:05 UTC
    If it is indeed homework then I expect the tutor wanted you to set the Listen parameter to 5 (5 is a magic number with listen). However it only sets the size of the connecting queue, it does not include clients already connected.
Re: A suicidal parent OR death of a forking server
by MidLifeXis (Monsignor) on Feb 09, 2010 at 14:33 UTC

    This sounds very much like a homework assignment. Not saying it is, but if so, please say so, as the types of answers you will get may be different (directed toward having you think through the answer, rather than giving the answer directly, for example).

    However, nice job on explaining your problem and providing the information about what you have already tried.

    It is said that "only perl can parse Perl." I don't even come close until my 3rd cup of coffee. --MidLifeXis

      Please see my comments to the original post. Thanks!
Re: A suicidal parent OR death of a forking server
by MonkeyMonk (Sexton) on Feb 10, 2010 at 09:44 UTC
    @MidLifeXis:

    Definitely not a homework assignment. I can understand the reasons for the question though.

    The above code is just one side of the program. The other side has a RS232 serial interface( and that is why I mentioned "enable queuing of commands" ). Apologies for not mentioning this earlier : my intentions were never to mislead the monks! I thought it is best to tackle one issue at a time instead of going into the details.

    I do not know how others dealt with things when facing forking and threads. Personally I have been using Perl for quite some time now and can understand it to a fair degree. _However_, when dealing with forks/threads, it requires a "fundamental" shift in the way we think. "Fundamental" might be apt when I look at it from this of the fence. When I reach the other side, I might see it differently. But until then, it is still fundamental for me :)

    @BrowserUk (Apostle) :
    When dealing with RS232 interfaces, there is indeed a problem of "waiting" and then there is this perennial issue of ensuring that the interface is alive. I will have to look into the various scenarios and see how I can implement queuing.

    Many thanks everyone.
    I still haven't opened my dictionary to check the meanings of the words here. I think I will be occupied for quite some time just going through the replies and understanding what is being said!

Re: A suicidal parent OR death of a forking server
by MonkeyMonk (Sexton) on Feb 10, 2010 at 09:49 UTC

    Oh I almost forgot to ask: Can someone recommend good books on forking/threads.

    Thanks!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://822182]
Approved by Ratazong
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (5)
As of 2024-03-19 08:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found