http://qs321.pair.com?node_id=485446

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

Hi, I'm new to perl (allthough Im pretty good with php and delphi) and have been making some small programs. I just started a program and allready am stuck. I am trying to make a server/client app. All that th server does is accept connections, and on reading data from a client, sends it to every other one. The client should connect to the server, receive data from it, and send data to it, a very very simple chat application in short. I am having problems with the server, to manage the different connections, I am using the select() function, as described here: http://www.perlfect.com/articles/select.shtml My code allmost exactly the same as that, and it still deosnt work, for a start I would like it to print the data received from the clien on the screen, what is wrong here (it never prints the data to the screen)?:
#!/usr/bin/perl #test script use IO::Socket; use IO::Select; if(@ARGV < 1) { printf("Usage:\ntest.pl <port to run on>\n"); exit(); } my $sock = IO::Socket::INET->new( Proto=>"tcp", LocalHost=>"Localhost", Listen=>16, Reuse=>1, LocalPort=>$ARGV[0] ) or die("Could not create socket!\n") +; my $readSet = new IO::Select(); $readSet->add($sock); while(1) { my $rhSet = IO::Select->select($readSet, undef, undef, 0); foreach $rh(@rhSet) { if($rh == $sock) { $newSocket=$rh->accept(); $readSet->add($newSocket); } else { $buf=<$rh>; if($buf) { printf "$buf"; } else { $readSet->remove($rh); close($rh); } } } }
I have allready anticipated another problem I will have. As you see, it accepts any new sockets, but what if, I want to send data to all open sockets the server has received? I see no where (array, etc) where the sockets are saved. Any help?

Replies are listed 'Best First'.
Re: New to perl: IO Select question :(
by Roger (Parson) on Aug 21, 2005 at 00:00 UTC
    I think the most likely problem in your code is here:
    my $rhSet = IO::Select->select($readSet, undef, undef, 0); foreach $rh(@rhSet) {
    Should it be my @rhSet = ...? Your @rshSet was never set in your script. It's a common error for new Perl programmers. You can catch these kinds of errors if you turn on the strict pragma with use strict; at the top of your code.

    I recommend to use the cpan module Net::Server to write your server. Unless you are learning socket level programming, why bother with low level stuff when you have a well written module that does pretty much everything you want?

      His code will never accept any connection, thus never enter the while loop. Without fixing that, but only focus on issues inside the while loop will not make his code work.

      Thanks for the reply. Making it an array (@) still doesnt make a difference :( Yeh when I was reading a perl tutorial they mentioned that all variables must be declared with a "my" infront of them, but then reading the select() tutorial, some variables used my, some didnt so that confused me, I just copied the code. Any more suggestions? **EDIT** Yes, i am learning network programming, but il take a look at net::server thanks
        Try this instead, in case the select method returns a reference to an array:
        my $rhSet = IO::Select->select($readSet, undef, undef, 0); foreach $rh(@$rhSet) {


        You should also read the perldoc for IO::Select, there is a short example at the end.
        perldoc IO::Select EXAMPLE Here is a short example which shows how "IO::Select" could be used to write a server which communicates with several sockets while also listening for more connections on a listen socket use IO::Select; use IO::Socket; $lsn = new IO::Socket::INET(Listen => 1, LocalPort => 8080); $sel = new IO::Select( $lsn ); while(@ready = $sel->can_read) { foreach $fh (@ready) { if($fh == $lsn) { # Create a new socket $new = $lsn->accept; $sel->add($new); } else { # Process socket # Maybe we have finished with the socket $sel->remove($fh); $fh->close; } } }

Re: New to perl: IO Select question :(
by pg (Canon) on Aug 21, 2005 at 00:32 UTC

    You never had the opportunity to accept any connection from the client side. Add $sock to IO::Select is no good, as it is not an "active channel" yet. Add the accept(), plus fix some minor syntax stuff, your code works.

    use strict; use warnings; use IO::Socket; use IO::Select; my $sock = IO::Socket::INET->new( Proto=>"tcp", LocalHost=>"localhost", Listen=>16, Reuse=>1, LocalPort=>3000 ) or die("Could not create socket!\n") +; my $connection = $sock->accept(); my $readSet = new IO::Select(); $readSet->add($connection); while(1) { my @rhSet = IO::Select->select($readSet, undef, undef, 0); foreach my $rh (@{$rhSet[0]}) { my $buf=<$rh>; if($buf) { printf "$buf"; } else { $readSet->remove($rh); close($rh); } } }

    Client side test code:

    use IO::Socket; my $sock = IO::Socket::INET->new( Proto=>"tcp", PeerHost=>"localhost", PeerPort=>3000 ) or die("Could not create socket!\n") +; print $sock "abcd\n";

    Well, I leave it to you to add back the code to accept further connections.

      I don't think you're right on this one. His code works fine as long as you just replace

      my @rhSet = IO::Select->select($readSet, undef, undef, 0); foreach my $rh (@{$rhSet[0]}) {

      with (as in your example)
      my @rhSet = IO::Select->select($readSet, undef, undef, 0); foreach my $rh (@{$rhSet[0]}) {

      The

      my $connection = $sock->accept(); $readSet->add($connection);
      you added are not needed, and I'd personally consider doing that more harmful than useful.

      I also don't understand your comment about $sock not being an "active channel". After you create a server socket you can add it to the read set perfectly fine, and the select will return the socket when there's a connection to accept.

        For server side, one cares whether each connection can read or write, not the listening server socket. The listening socket does not read or write.

        Let's do a simple testing. With the following client and server:

        #server use strict; use warnings; use IO::Socket; use IO::Select; my $sock = IO::Socket::INET->new(Proto=>"tcp", LocalHost=>"localhost", + Listen=>16, Reuse=>1, LocalPort=>3000) || die("Could not create socket!\n"); my $conn = $sock->accept(); my $sel = new IO::Select($sock); if ($sel->can_read()) { print "can read\n"; } #client use IO::Socket; my $sock = IO::Socket::INET->new( Proto=>"tcp", PeerHost=>"Localhost", PeerPort=>3000 ) or die("Could not create socket!\n") +; print $sock "abcd\n";

        Run perl -w server.pl in one window, then run perl -w client.pl in another window, nothing gets printed in the server window! run perl -w client.pl again, "abcd" gets printed. This is not what one will want.

        change server code:

        use strict; use warnings; use IO::Socket; use IO::Select; my $sock = IO::Socket::INET->new(Proto=>"tcp", LocalHost=>"localhost", + Listen=>16, Reuse=>1, LocalPort=>3000) || die("Could not create socket!\n"); my $conn = $sock->accept(); my $sel = new IO::Select($conn); if ($sel->can_read()) { print "can read\n"; }

        The first time you run perl -w client.pl, server side knows that it can read, and prints "abcd". That's what one wants.

Re: New to perl: IO Select question :(
by zentara (Archbishop) on Aug 21, 2005 at 12:39 UTC
    In case you havn't found the answer yet, here is a working script for you to test. If the script below dosn't work, you may have poor clients. I included a compatible client below the server.

    Server:

    #!/usr/bin/perl use IO::Socket; use IO::Select; my @sockets; my $machine_addr = '192.168.0.1'; $main_sock = new IO::Socket::INET(LocalAddr=>$machine_addr, LocalPort=>1200, Proto=>'tcp', Listen=>3, Reuse=>1, ); die "Could not connect: $!" unless $main_sock; print "Starting Server\n"; $readable_handles = new IO::Select(); $readable_handles->add($main_sock); while (1) { ($new_readable) = IO::Select->select($readable_handles, undef, undef +, 0); foreach $sock (@$new_readable) { if ($sock == $main_sock) { $new_sock = $sock->accept(); $readable_handles->add($new_sock); } else { $buf = <$sock>; if ($buf) { print "$buf\n"; my @sockets = $readable_handles->can_write(); #print $sock "You sent $buf\n"; foreach my $sck(@sockets){print $sck "$buf\n";} } else { $readable_handles->remove($sock); close($sock); } } } } print "Terminating Server\n"; close $main_sock; getc();

    Client: #######################################

    #!/usr/bin/perl -w use strict; use IO::Socket; my ( $host, $port, $kidpid, $handle, $line ); ( $host, $port ) = ('192.168.0.1',1200); my $name = shift || ''; if($name eq ''){print "What's your name?\n"} chomp ($name = <>); # create a tcp connection to the specified host and port $handle = IO::Socket::INET->new( Proto => "tcp", PeerAddr => $host, PeerPort => $port ) or die "can't connect to port $port on $host: $!"; $handle->autoflush(1); # so output gets there right away print STDERR "[Connected to $host:$port]\n"; # split the program into two processes, identical twins die "can't fork: $!" unless defined( $kidpid = fork() ); # the if{} block runs only in the parent process if ($kidpid) { # copy the socket to standard output while ( defined( $line = <$handle> ) ) { print STDOUT $line; } kill( "TERM", $kidpid ); # send SIGTERM to child } # the else{} block runs only in the child process else { # copy standard input to the socket while ( defined( $line = <STDIN> ) ) { print $handle "$name->$line"; } }

    I'm not really a human, but I play one on earth. flash japh