#!/usr/bin/perl -w -s ################### # serverDemo - half of a demo tcp/ip socket demo # # usage: # # Start serverDemo listening from the command line like this # # shell% ./serverDemo & # # You can use the unix shell command "netstat -a" to see that it's # listening on the port given below as $localport. # # Then use demoClient to connect to it. # See its documentation for the details. # # implementation: # # Writing a server is more work than writing a client. # A server needs to do something like this: # (1) create a socket ($sockListen) # to listen at a local port for clients connecting to it. # (2) When a client connects, another socket ($sockClient) # is created to talk to that particular client. # (3) Now things get tricky. The server should now do several things # at once: listen for more connections, and talk to each client. # Typically there are two approaches. Either # (a) in parallel: fork a new process # (or create a new thread) to deal with each client, or # (b) in serial: loop through all sockets, simultaneously # listening for new connections and talking to each client, # in brief spurts. In this case, we must have non-blocking # socket calls pretty much throughout - no waiting # The advantages to (a) are # * typically simpler code, # * let the OS or language worry about time sharing, # * can do expensive operations with one client without worrying about # leaving others hanging # while the disadvantages to (a) are # * forking is memory intensive and may be slow # * communication between different client handlers, and # how they share resources (databases, files, ...) can be tricky # The serial method can work well when each interaction is quick; # otherwise, usually the parallel method is chosen. # # Here I show the serial approach, using non-blocking calls # and a hash of open connections. We listen for new ones and deal # with each open connection in turn. # # See "perldoc perlipc", "perldoc IO::Socket::INET" for documentation. # # - Jim Mahoney, Marlboro College, Jan 2002 ################### use strict; use IO::Socket; my $EOL = "\015\012"; # End of Line. See comments in simpleClient. my $localport = 3003; # Socket that this server listens on. print STDOUT "SERVER: starting serverDemo \n"; my $sockListen = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $localport, Listen => 5, Reuse => 1, ) or die "SERVER: Couldn't listen at port $localport; $@"; print STDOUT "SERVER: accepting clients at port $localport. \n"; # In addition to the arguments described in simpleServer, # INET->new() uses these for the server: # Listen => number max number of clients waiting to be accepted # LocalPort => number port that we listen at (0->1024 are root only) # Reuse => true gives a quicker manual server restart # # And here are a few more IO::Socket::INET methods: # $clientSocket = $sockListen->accept() # return a new socket if a # # client is waiting to connect # $clientHost = $sockClient->peeraddr() # ip address of client # # # an API to get some info about the connected client # use Net::hostent; # $hostinfo=gethostbyaddr( $sockClient->peeraddr() ); # $hostname = $hostinfo->name; # Subroutine to autoflush and print some diagnostics for a socket. sub setupSocket { my ($socket, $name) = @_; $socket->autoflush(1); # write everything out immediately. $socket->blocking(0); # don't wait when accepting or reading my $block = $socket->blocking; $block = 'undef' unless defined($block); print STDOUT "SERVER: $name blocking : '$block' \n"; } setupSocket($sockListen, 'sockListen'); my %clients; # hash of unique-ids => $clientSocket my %times; # hash of unique-ids => last_activity_time my $tooOld = 60; # time (seconds) when connections are stale my $n=0; # number of sockets which have been created # Now we enter an infinite loop to do all the dirty work. while (1) { # Check (briefly) to see if someone new wants to join in. # If so, add him to the hash and send him some text. if ( my $sockClient = $sockListen->accept ) { $n++; # give him a new number setupSocket($sockClient, 'sockClient'); # set him up my $ipkey = $n . "-" . $sockClient->peeraddr; # his unique ID $clients{$ipkey} = $sockClient; # add him to the client hash $times{$ipkey} = time(); # remember the time my $toClient = "Hi. Server time is " . scalar localtime(); print STDOUT "SERVER: sending to $ipkey '$toClient'\n"; print $sockClient $toClient . $EOL; # send out initial greeting } # Loop over the connected clients. # As long as the connection is open and not too old, # echo any lines sent by the client. foreach my $ipkey (keys %clients) { my $sockClient = $clients{$ipkey}; # get client from hash my $fromClient = <$sockClient>; # try to read from it. if ( defined($fromClient) and $fromClient==0 ) { # defined but 0 => EOF print STDOUT "SERVER: $ipkey closed on client end\n"; close($sockClient); delete $clients{$ipkey}; # and remove him. delete $times{$ipkey}; } elsif ( $fromClient ) { # look for a line s/\012//, s/\015// for $fromClient; # clean it up print STDOUT "SERVER: $ipkey receieved '$fromClient'\n"; # ouptut it $times{$ipkey} = time(); # remember the time. } elsif ((time()-$times{$ipkey})>$tooOld) { # if his time is stale, print STDOUT "SERVER: closing stale $ipkey \n"; # print to stdout, close($sockClient); # close, delete $clients{$ipkey}; # and remove him. delete $times{$ipkey}; } # process client i/o # # A tricky point here is differentiating betwee # the end of file and an empty return from a non-blocking call. # # One partial fix is to have an agreed upon signal in the # protocol when we're done. # # There's a discussion of how this looks at the lower, sysread # level in Lincoln Stein's Network Programming book, on # page 363. It says: # When reading from non-blocking handles you must successfully # distinguish between the end-of-file condition (which returns # numeric 0 from sysread()) and the EWOULDBLOCK error (which # returns undef from sysread)). } } # We never get here, so the sever can only be shut down manually.