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

In the past, there has been a lot of discussion on the site about client/server applications written in perl. For the most part, the examples presented on PM use fork() to pass client connections to a child process so that the parent can continue to accept new connections (for multiple clients handled at the same time, not one after another).

So now it's my turn to ask some questions about client/server programming in perl. Here's what I am trying to accomplish:

Thanks to This post, I now have the following code:

# server.pl use strict; use IO::Socket; use IO::Select; my $main_sock = new IO::Socket::INET( LocalAddr => '127.0.0.1', LocalPort => 1200, Proto => 'tcp', Listen => 1, ReuseAddr => 1, ); die "Could not initialize server: $!\n" unless $main_sock; print "Server has successfully initialized.\n\n"; my $r_handles = new IO::Select(); $r_handles->add( $main_sock ); while (1) { my ($new_r) = IO::Select->select( $r_handles, undef, undef, 0 ); foreach my $sock (@$new_r) { if ($sock == $main_sock) { my $new_sock = $sock->accept(); $r_handles->add($new_sock); } else { my $buf = <$sock>; if ($buf) { print "$buf\n"; } else { $r_handles->remove($sock); close $sock; } } } } # client.pl use strict; use IO::Socket; my $client_id = $ARGV[0]; my $sock = new IO::Socket::INET( PeerAddr => '127.0.0.1', PeerPort => 1200, Proto => 'tcp', ); die "Could not connect to server: $!\n" unless $sock; for (1 .. 5) { print $sock "From $client_id: Msg $_\n"; sleep 1; } close $sock;

Okay, so there is no fork() here and it successfully allows multiple clients to be connected at any given time. The problem I've run into is allowing data to flow both ways on the connection. I do know a little bit about sockets (though not nearly as much as I'd like) and know that I will most likely have to setup the second communication line on another port. This would be fine. So what is my guess as to what I would do? More or less mix the two scripts together so that I have one listener and one sender on each side of the connection. The part I just cannot figure out for the life of me is where I would/could/should mix the scripts together. The current server (that receives data from the client) is in an infinite loop, therefore I'd have to send data somewhere within that loop. But where and exactly how? I also understand that if I do end up having 2 connections in each script that I need the IP address for both sides to get things going. But don't worry, I already have a sneeky way to do that.

I have now confused myself to death over this, and I'd really love to discover a solution to this. I've wanted a bi-directional client/server app for a while, but not until today did I find those examples that do not require the use of fork(). Of course, if WIn32 properly supported fork, I would not have to ask this, but you know how things are :) Help!

Replies are listed 'Best First'.
Re: Non-forked, bi-directional client/server
by jepri (Parson) on Jan 20, 2003 at 06:18 UTC
    POE is indeed shiney good value. But to answer your other questions, you don't need two ports, you can just read and write to the same socket. The data won't get mixed up.

    As regards shifting information around, it is traditional to do "callbacks" - calling a subroutine to process data that you read, calling a subroutine to prepare some data, and then sending the prepared data. Things like event queues and packet queues are sometimes useful.

    The big reason not to do this is that while you are doing anything, you aren't waiting for the next connection, or the next packet of data. This will probably only show up under load i.e. when you use it for real for the first time.

    This is the way it tends to go:

    while (1) { $new_conn = do_select(); add_connection( $new_conn ) if $new_conn; read_all_connections(); process_data(); write_all_connections(); }

    but it gets very messy since you have to maintain state information for each socket, etc. Which is why a object interface like POE is a great idea.

    ____________________
    Jeremy
    I didn't believe in evil until I dated it.

•Re: Non-forked, bi-directional client/server
by merlyn (Sage) on Jan 20, 2003 at 05:59 UTC
Re: Non-forked, bi-directional client/server
by Corion (Patriarch) on Jan 20, 2003 at 08:31 UTC

    Preamble: I admit, I find event driven programming ugly. It destroys the natural flow of the program by breaking up loops and turning them into three separate pieces of code. Simple, sequential tasks also get broken up and scattered around. This is why I find HTTP and POE ugly. But, as you have seen, event driven programming can give good results, that's why one resorts to it. I'm always looking for good alternatives to event driven programming :

    Fresh on the CPAN is Net::Socket::NonBlock, but it is in a really early version (and I haven't played with it at all). Depending on its API, it might implement enough buffering such that your program flow won't have to take much into account that it is really multiple sessions.

    Another option to consider is the Coro module - coroutines (and, much more interesting, cooperative multitasking) in Perl. Its performance isn't as high as it could be, and it isn't as portable as it could be (I don't know if it works on Win32 now :-(), but it allows you easy threaded programming without the headaches of real multithreading. The only drawback is, of course, that disk IO is still synchronous, so if disk IO becomes the bottleneck, you're maybe better off to spawn a separate IO worker process and connect to that via another socket. I have played with the Coro module, but not under Win32.

    The third option would be to use a database for synchronisation. This has the drawback of being really slow, but it has the advantage of being resumable if your program crashes. iI you're looking for a "lightweight" database, take a look at DBD::SQLite - but if you're doing much writing to that database, you will spend more time waiting for the file locks than actually doing anything else.

    perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web
Re: Non-forked, bi-directional client/server
by castaway (Parson) on Jan 20, 2003 at 09:53 UTC
    I gave an example of this here: Re: Daemons in Perl basically what you need to do, is instead of:
    } else { my $buf = <$sock>; if ($buf) { print "$buf\n";
    Call a function with $buf and the socket_object as parameters, the function can then parse whatever it got, and answer by writing to the socket with print $socket.
    In the client you also need an IO::Select and a loop using select (or can_read() as I did), and do essentially the same thing.
    I've written a few client/server scripts, and never used fork(), if you want to simultaneous two-way conversation, you can also use Threads, which I've also used, (The 5.005 version at the mo, still trying to figure out how to convert to ithreads..)
    C.
Re: Non-forked, bi-directional client/server
by Veachian64 (Scribe) on Jan 20, 2003 at 15:28 UTC
    I wrote The Veachian IRC Daemon specifically to run on Windows, so it doesn't use forking of any kind. You should be able to connect to it with any standard IRC software. I've had at least 15 people connected to it at one time with no problems. Each client can send commands and message other people on the server with almost no lag.
Re: Non-forked, bi-directional client/server
by pg (Canon) on Jan 20, 2003 at 15:58 UTC
    1. Always give multi-threading a try whenever you are thinking of fork.

      I am not saying you should never use fork, but look back at the history, one big reason that fork was/(unfortunately is) heavily used is that Perl did not support multi-thread (very well), which is getting improved from revision to revision.

      Breaking the old norms, and re-establish our norms on the new ground, especially for Perl newbies do not have a burden of history.

      Always look at both fork and multi-thread, make your decision on a case by case base.
    2. The other thing is that, taking fork and multi-thread as helper, not restriction. It is obvious that, you can archive what you want to do here without multi-thread or fork.

      Neither is it said that fork or multi-thread is a must, nor is it said that multi-thread or fork is the best. Look at the benefits that multi-thread and fork may give you, and make your design decision. Don't use those techniques, just because they are cool, and don't refuse to use them, just because they are a little bit more complex.
Re: Non-forked, bi-directional client/server
by Excalibor (Pilgrim) on Jan 21, 2003 at 11:01 UTC

    Hi there,

    Have a look at CPAN for C<Net::Server>. It provides a host of different working models, and it's really easy to use, I am using it in real-life network projects and I think it's a truly useful module.

    From the POD:

    FEATURES * Single Server Mode * Inetd Server Mode * Preforking Simple Mode (PreForkSimple) * Preforking Managed Mode (PreFork) * Forking Mode * Multiplexing Mode using a single process * Multi port accepts on Single, Preforking, and Forking modes * Simultaneous accept/recv on tcp, udp, and unix sockets * Safe signal handling in Fork/PreFork avoids perl signal trouble * User customizable hooks * Chroot ability after bind * Change of user and group after bind * Basic allow/deny access control * Customized logging (choose Syslog, log_file, or STDERR) * HUP able server (clean restarts via sig HUP) * Dequeue ability in all Fork and PreFork modes. * Taint clean * Written in Perl * Protection against buffer overflow * Clean process flow * Extensibility

    Check out the lastest version at http://seamons.com/

    Good luck,
    xc

    our $Perl6 is Fantastic;