7.4.2.6 Forking An Interactive Server
7.4.2.6 Forking An Interactive Server
We will now modify the interactive server discussed in section 7.4.2.4 so that the server is forked, and as a result, it can service multiple clients at the same time. The program discussed in that section could service only one client at a time. If more clients attempt to connect, it put all but the first client, the one currently being serviced on hold, in the listen queue. When the current client is finished talking, only then the second client is serviced. This is not a desirable state of affairs since a more robust server should be more efficient and should be able to handle several interactive clients at the same time.
Below, we present a modified version of the interactiveServer.pl program discussed in section 7.4.2.4. It is now called forkedServer.pl. The code is given below.
Program 7.35
#!/usr/bin/perl
#forkedServer.pl
use IO::Socket;
use Net::hostent;
use strict;
$SIG{CHLD} = sub {wait ();};
my $PORT = 2000; #Pick something not used
my %socketDescription = (Proto => 'tcp',
LocalPort => $PORT,
Listen => SOMAXCONN,
Reuse => 1);
my $server = IO::Socket::INET -> new (%socketDescription);
die "Cannot set up server" unless $server;
print "[Interactive server $0 accepting clients]\n";
my ($client, $processID);;
while ($client = $server -> accept()){
$processID = fork ();
die "Cannot fork: $!" unless defined ($processID);
next if ($processID > 0);
#In the child process
serviceAClient ($client);
close $client;
}
#########################################
#Subroutines
#Read a file and return its contents
sub readFile {
my ($fileName) = @_;
open (IN, $fileName);
my @contents = ;
return @contents;
}
#Service a client connection
sub serviceAClient{
my ($socket) = @_;
print $socket "Welcome to server $0 on $ENV{'HOST'}\n";
print $socket " type ? for command list\n";
my $hostInfo = gethostbyaddr ($socket -> peeraddr());
print "[Connect from ", $hostInfo -> name, "]\n";
while (my $socketCommand = <$socket>){
if ($socketCommand =~ /localtime/){
print $socket scalar (`date`), "\n";
}
elsif ($socketCommand =~ /passwd/){
print $socket readFile ('/etc/passwd');
}
elsif ($socketCommand =~ /services/){
print $socket readFile ('/etc/services');
}
elsif ($socketCommand =~ /who/){
print $socket scalar (`/usr/bin/who`), "\n";
}
elsif ($socketCommand =~ /quit|q/){
print "[Disconnect with ", $hostInfo -> name, "]\n";
last;
}
else {
print $socket "Commands are: passwd localtime services who quit\n";
}
}
}
It is essentially the same program as the previous one, except that we fork the server, and that we have broken up the client servicing code into a subroutine called serviceAClient. We will not discuss the details of this subroutine because it has been discussed at length in section 7.4.2.4.
Let us look at the main program and focus on the while loop that is executed after a listening socket has been successfully set up. This while loop is repeated below.
while ($client = $server -> accept()){
$processID = fork ();
die "Cannot fork: $!" unless defined ($processID);
next if ($processID > 0);
#In the child process
serviceAClient ($client);
close $client;
}
In the conditional of the while loop, the server waits to accept a client that requests its attention. When a client requests to talk, a dedicated socket $client is set up to communicate with it. The new socket $client is exactly like the listening socket $server. Inside the while loop, the server process immediately
forks to produce a child process with ID $processID. After the fork, both the parent and the child processes run the same code. Both processes die if the forking was unsuccessful.
The two processes have a copy of the variable $processID each. The child sees a value of 0, and the parent sees a positive integer value corresponding to the process ID of the child. Next, the statement
next if ($processID > 0);
is executed. This statement causes the parent process to go to the top of the while loop for the next iteration, and wait to accept additional incoming connections. Thus, the child process is the only one that calls the subroutine serviceAClient with the new dedicated socket $client as the argument. Once the client is serviced, the child process dies automatically.
The corresponding client program is a slight modification of the program interactiveClient.pl discussed in section 7.4.2.4. The new client program is given below.
Program 7.36
#!/usr/bin/perl
#interactiveClient1.pl
#Creates a socket, keeps it interactive so that a
#user types commands and get replies
use strict;
use IO::Socket::INET;
use Net::hostent;
my ($interactiveSocket, $childProcessID);
my $peerAddr = "cs.uccs.edu";
$interactiveSocket = new IO::Socket::INET (
PeerAddr => $peerAddr,
PeerPort => 2000,
Proto => 'tcp',
Timeout => 30
);
print "Established socket connection to $peerAddr\n";
die "Couldn't connect: $!" unless $interactiveSocket;
my $hostInfo = gethostbyaddr ($interactiveSocket -> peeraddr);
my $hostName = $hostInfo->name();
print "[Created a TCP socket with $hostName]\n";
$childProcessID = fork ();
die "Fork failed: $!" unless defined ($childProcessID);
if ($childProcessID > 0){
#parent
#copy the socket to standard output. This lets us see what
#comes back to the socket from the server
while (defined (my $responseLine = <$interactiveSocket>)){
print STDOUT $responseLine;
}
#When the remote server closes its connection, send a TERM signal
#to the child process and kill it.
kill ("TERM", $childProcessID);
#Eliminate the socket as as soon as the server closes its end.
#Also, exit the process.
callItQuits ($interactiveSocket);
}
else{
#child process reads from STDIN
#copy standard input to the socket. This lets us type a command
#on the screen and have it written to the socket, i.e., sent to the server
while (defined (my $command = )){
chomp $command;
print $interactiveSocket "$command\r\n"; #\r\n is needed on Internet
if ($command =~ /quit/){
callItQuits ($interactiveSocket);
}
}
}
##########subroutines
sub callItQuits{
#Shutdown the specified socket and kill the process
my ($socket) = @_;
#Shut the socket down for input as well as output
shutdown ($socket, 2);
exit 0;
}
This program is essentially the same as the program interactiveClient.pl. The only difference is that we now have a subroutine called callItQuits that takes a socket as an argument, shuts it down for reading as well as writing, and exits the current process with a 0 indicator or in good health. This subroutine is called both by the parent and the child processes after forking. We know from our discussions in section 7.4.2.4 that the parent reads the socket
$interactiveSocket for responses coming back from the server and displays them on the terminal in which the client program is running. We also know that the child reads what the user types in at the terminal and sends it to the distant server.
In the new version, the subroutine callItQuits is called by the parent process if the server is killed to shut down the socket and to exit the parent process. This is done after killing the child process. This was done in the previous version of the program as well. In this new version of the program, the child process also calls callItQuits if the child reads quit at the client terminal.
We can start the server on a particular machine, say cs.uccs.edu. It waits to hear from clients. When the first client calls, the server forks, and the newly minted child process services the client call. The parent process goes back and listen for additional connection requests from clients. If a second client calls as the first one is being serviced, the parent process forks again. The second child services the second connection request, and so on and so forth. Thus, the parent process acts like a receptionist at a company
or organization, taking the initial call and transferring it to the person the call is intended for, so that there is a direct line of communication between the external caller and the intended recipient within the company or the organization. This allows the receptionist to take additional calls even as the first call proceeds using its “dedicated” line of communication.
