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

Hello, I was wondering of there are any socket gurus who might be willing to give the following a quick glance to make sure I'm going about this the right way. This is an echo server.
#!/usr/bin/perl -w use IO::Select; use IO::Socket; use strict; $|++; my $port = 9999; my $server = IO::Socket::INET->new ( Proto => 'tcp', LocalPort => $port, Listen => SOMAXCONN, Reuse => 1); (! $server) && die "Could not setup server - $!\n"; $server->autoflush(1); while (my $client = $server->accept()) { my $request; my $selector = IO::Select->new($client); if ($selector->can_read(10)) { my $buffer; while (sysread($client, $buffer, 1024)) { $request .= $buffer; } } else { print "Selector *cannot* read or timed out after 10 seconds.\n"; next; } #print $request, "\n"; syswrite($client, $request); $client->shutdown(1); }
Specifically I'm wondering if sysread and syswrite are what are should be using. I've seen server code that uses while (<$client>) { instead of sysread and print $client $request instead of syswrite. I think I'm confused as to which one is better.

Brutus.

Replies are listed 'Best First'.
Re: socket - echo server
by pfaut (Priest) on Jan 17, 2003 at 23:24 UTC

    This is not normally how you would write a multi-client server. Your code accepts a connection, waits up to 10 seconds for data to arrive, sends the data back if it arrives in time, then disconnects and goes back to wait for the next connection. This is handling clients serially. If I were to attempt 60 connections to your server but not send any data on any of those connections, any other clients would be hung for up to 10 minutes before you to timed out all of my requests.

    Normally, the server socket would be included in the select set. When that socket becomes ready, you can issue an accept on it. If another socket becomes ready, you can issue a read on it. Timeout handling becomes a little more difficult.

    I just hacked this up and it seems to work.

    #!/usr/bin/perl -w use strict; use IO::Select; use IO::Socket; use constant ECHO_PORT => 9999; my %clients; my $count; my $srvr = IO::Socket::INET->new(Proto=>'tcp', LocalPort=>ECHO_PORT, Listen=>SOMAXCONN, Reuse=>1) or die "Error creating server socket: $!"; $srvr->autoflush(1); my $sel = IO::Select->new($srvr); while (1) { my @rdy = $sel->can_read(1); if (scalar @rdy == 0) { # remove timed out clients my $t = time(); for my $cli (keys %clients) { if ($clients{$cli}{timeout} <= $t) { $sel->remove($clients{$cli}{conn}); $clients{$cli}{conn}->close(); delete $clients{$cli}; } } } for my $cli (@rdy) { if ($cli == $srvr) { my $cli = $srvr->accept; unless ($cli) { die "Error accepting client connection: $!\n"; next; } $clients{$cli}{conn} = $cli; $clients{$cli}{timeout} = time() + 10; $sel->add($cli); } else { my $buffer; if (sysread ($cli,$buffer,1024)) { syswrite($cli,$buffer); } else { warn "Error reading from client: $!\n"; } $sel->remove($cli); $cli->close(); delete $clients{$cli}; } } }
    --- print map { my ($m)=1<<hex($_)&11?' ':''; $m.=substr('AHJPacehklnorstu',hex($_),1) } split //,'2fde0abe76c36c914586c';
      While the OP didn't mention anything about the code needing to handle multiple concurrent clients, adding that functionality is probably a good idea. I'd probably fork to handle concurrent clients. Probably something like this (untested):
      #!/usr/bin/perl -w use IO::Select; use IO::Socket; use POSIX qw(WNOHANG); use strict; $|++; my $port = 9999; my $server = IO::Socket::INET->new ( Proto => 'tcp', LocalPort => $port, Listen => SOMAXCONN, Reuse => 1); (! $server) && die "Could not setup server - $!\n"; $server->autoflush(1); while (my $client = $server->accept()) { my $kidPid; unless (defined($kidPid = fork())) { # error handling code here } if ($kidPid == 0) { my $request; my $selector = IO::Select->new($client); if ($selector->can_read(10)) { my $buffer; while (sysread($client, $buffer, 1024)) { $request .= $buffer; } } else { print "Selector *cannot* read or timed out after 10 seconds.\ +n"; exit(); } syswrite($client, $request); $client->shutdown(1); exit(); } close($client); meetTheReaper(); } sub meetTheReaper { # reap availible children while (waitpid(-1, WNOHANG) > 0) { } }
      -- vek --
Re: socket - echo server
by pg (Canon) on Jan 18, 2003 at 22:09 UTC
    I personally prefer multi-thread, not fork, considering fork is much more expansive.

    server.pl: use strict; use threads; use IO::Socket::INET; $| ++; my $listener = IO::Socket::INET->new(LocalPort => 3126, Listen => 5, Reuse => 1) || die "Cannot create socket\n"; my $client; my $client_num = 0; while (1) { $client = $listener->accept; threads->create(\&start_thread, $client, ++ $client_num); } sub start_thread { my ($client, $client_num) = @_; print "thread created for client $client_num\n"; while (1) { my $req; $client->recv($req, 700000); return if ($req eq ""); print $client $req; } return; } client.pl: use strict; use IO::Socket; my $server = IO::Socket::INET->new(Proto => "tcp", PeerPort => 3126, PeerAddr => "localhost", Timeout => 2000) || die "failed to connect\n"; for (1..100) { print $server $_; my $res; $server->recv($res, 70000); print $res; }