Using theinetdSuper Daemon


 
Network Programming with Perl
By Lincoln  D.  Stein
Slots : 1
Table of Contents
Chapter  10.   Forking Servers and the inetd Daemon

    Content

Using the inetd Super Daemon

Let's go back to Figure 10.3 and take a second look at the interact() function:

 sub interact {   my $sock = shift;   STDIN->fdopen($sock,"<")  or die "Can't reopen STDIN: $!";   STDOUT->fdopen($sock,">") or die "Can't reopen STDOUT: $!";   STDERR->fdopen($sock,">") or die "Can't reopen STDERR: $!";   $=1;   my $bot = Chatbot::Eliza->new;   $bot->command_interface(); } 

The psychotherapist daemon is pretty generic in its handling of incoming connections and forking. In fact, interact() is the only place where application-specific code appears.

Now consider this version of interact() :

 sub interact {   my $sock = shift;   STDIN->fdopen($sock,"<")  or die "Can't reopen STDIN: $!";   STDOUT->fdopen($sock,">") or die "Can't reopen STDOUT: $!";   STDERR->fdopen($sock,">") or die "Can't reopen STDERR: $!";   exec "eliza.pl"; } 

After reopening STDIN , STDOUT , and STDERR onto the socket, we simply exec() the original command-line eliza.pl script from Figure 10.2. Assuming that eliza.pl is on the command path , Perl launches it and replaces the current process with the new one. The command-line version of eliza.pl runs, reading user input from STDIN and sending the psychotherapist's responses to STDOUT . But STDIN , STDOUT , and STDERR are inherited from the parent process, so the program is actually reading and writing to the socket. We've converted a command-line program into a server application without changing a line of source code!

In fact, we can make this even more general by adding arguments to interact() that contain the name and command-line arguments of a command to execute:

 sub interact {   my ($sock,@command) = @>_;   STDIN->fdopen($sock,"<") or die "Can't reopen STDIN: $!";   STDOUT->fdopen($sock,">") or die "Can't reopen STDOUT: $!";   STDERR->fdopen($sock,">") or die "Can't reopen STDERR: $!";   exec @command; } 

Now any program that reads from STDIN and writes to STDOUT can be run as a server. For example, on UNIX systems, you could rig up a simple echo server just by passing /bin/cat as the argument to interact() . Since cat reads from STDIN and writes a copy to STDOUT , it will echo everything it reads from the socket back to the peer.

This simple way of creating network servers has not gone unnoticed by operating system designers. UNIX (and Linux) systems have a standard daemon called inetd , which is little more than a configurable version of this generic server capable of launching and running a variety of network services on demand.

inetd is launched at boot time. It reads a configuration file named /etc/inetd.conf , which is essentially a list of ports to monitor and programs to associate with each port. When a connection comes in to one of its monitored ports, inetd calls accept() to get a new connected socket, forks, remaps the three standard filehandles to the socket, and finally launches the appropriate program.

The advantage of this system is that instead of launching a dozen occasionally used services manually or at boot time, inetd launches them only when they are needed. Another nice feature of inetd is that it can be reconfigured on the fly by sending it a HUP signal. When such a signal arrives, it rereads its configuration file and reconfigures itself if needed. This allows you to add services without rebooting the machine.

Unfortunately, inetd is not standard on Win32 or Macintosh machines. For Windows, you can get inetd lookalikes at the following locations:

  • Cygnus Win32 tools

    ftp://go.cygnus.com/pub/ftp.cygnus.com/gnu-win32

    http://www.cygnus.com/misc/gnu-win32

  • Ockham Technology inetd for Windows NT (commercial)

    http://www.ockham.be/inetd.html

Many years ago I used an inetd lookalike for the Macintosh, which used Apple Events to simulate a true inetd daemon, but it no longer seems to be available on the Web.

Using inetd

With inetd we can turn the command-line psychotherapist program of Figure 10.2 into a server without changing a line of code. Just add the following line to the bottom of the /etc/inetd.conf configuration file:

 12000 stream tcp nowait nobody /usr/local/bin/eliza.pl eliza.pl 

You must have superuser access to edit this file. If there is no account named nobody, replace it with your login name (or another of your choosing). Adjust the path to the eliza.pl script to reflect its actual location (I suggest you use a version of the script that includes the _testquit() patch described earlier). When you're done editing the file, restart the inetd daemon by sending it a HUP signal. You can do this by finding its process ID (PID) using the ps command and then using the kill command to send the signal. For example:

 % ps aux  grep inetd root      657  0.0  0.8  1220  552 ?       S   07:07   0:00 inetd lstein    914  0.0  0.5   948  352 pts/1   S   08:07   0:00 grep inetd % kill -HUP 657 

Two shortcuts work on many Linux systems:

 % kill -HUP `cat /var/run/inetd.pid` % killall -HUP inetd 

Now you can use either the standard telnet program or the gab3.pl client developed earlier in this chapter to talk to the psychotherapist server.

Let's look at the inetd.conf entry in more detail. It's divided into seven fields delimited by whitespace (tabs or spaces):

12000 This is the service name or port number that the server will listen to. Be sure to check that your system isn't already using a port before you take it (you can use the netstat program for this purpose).

Some versions of inetd require you to use a symbolic service name in this field, such as eliza rather than 12000. On such systems, you must manually edit the file /etc/services , add the name and port number you desire , and then use that symbolic name in inetd.conf . For the psychotherapist daemon, an appropriate /etc/services line would be:

 eliza 12000/tcp 

We would then use eliza instead of the port number as the first field in inetd.conf.

stream This field specifies the server type, and can be either stream for connection-oriented services that send and receive data as continuous streams of data, or dgram for services that send and receive connectionless messages. Any program that reads STDIN and writes STDOUT is a stream-based service, so we use stream here.

tcp This specifies the communications protocol, and may be either tcp or udp (many systems also support more esoteric protocols, but we won't discuss them here). Stream-based services use tcp .

nowait This tells inetd what to do after launching the server program. It can be wait , to tell inetd to wait until the server is done before launching the program again to handle a new incoming connection, or nowait , which allows inetd to launch the program multiple times to handle several incoming connections at once. The most typical value for stream-based services is nowait , which makes inetd act as a forking server. If multiple clients connect simultaneously , inetd launches a copy of the program to deal with each one. Some versions of inetd allow you to put a ceiling on the number of processes that can run simultaneously.

/usr/local/bin/eliza.pl This is the full path to the program.

eliza.pl The seventh and subsequent fields are command-line arguments to pass to the script. This can be any number of space-delimited command-line arguments and switches. By convention, the first argument is the name of the program. You can use the actual script name, as shown here, or make up a different name. This value shows up in the script in the $0 variable. Other command-line arguments appear in the @ARGV array in the usual manner.

The main "gotcha" with inetd -launched programs is that stdio buffering may cause the data to flow unpredictably. For example, you might not see the psychotherapist's initial greeting until the program has output a few more lines of text. This is solved by turning on autoflush, as we did in Figure 10.2.

Using inetd in wait Mode

Using inetd in nowait mode is not as efficient as writing your own forking server. This is because inetd must launch your program each time it forks, and the Perl interpreter can take a second or two to launch, parse your script, and load and compile all the modules you require. In contrast, a forking server has already been through the parsing and compiling phases; therefore, the overhead of forking to handle a new connection is much less significant.

A nice compromise between convenience and performance is to use inetd in wait mode. In this mode it launches your server when the first incoming connection arrives, and waits for the server to finish. Your server will do everything an ordinary server does, including forking to handle new connections. The only difference is that it will not create the listening socket itself, but inherit it from one created by inetd . Since inetd duplicates the socket onto the three standard filehandles, you can recover it from any one of them, typically STDIN .

inetd thus nicely relieves you of the responsibility of launching the server by hand without incurring a performance penalty. In addition, you can write the server to exit under certain conditions ”for example, if it has been idle for a certain number of minutes, or after servicing a set number of connections. After it exits, inetd will relaunch it when it is next needed. This means that you need not keep an occasionally used server running all the time.

A new version of the psychotherapist server designed to be run from inetd in wait mode is given in Figure 10.8. The corresponding entry in /etc/inetd.conf is almost identical to the original, except that it uses wait in the fourth field and has a different script name in the sixth field:

Figure 10.8. inetd psychotherapist in wait mode

graphics/10fig08.gif

 12000 stream tcp wait nobody /usr/local/bin/eliza_inetd.pl eliza_inetd.pl 

In addition to inheriting its listening socket from inetd , this server differs from previous versions in having a one-minute timeout on the call to accept() . If no new connections arrive within the timeout period, the parent process exits. inetd will relaunch the server again if needed. The changes required to the basic forking server are small.

Lines 1 “7: Define timeout values We recover the timeout from the command line, or default to one minute if no value is supplied. Notice that we no longer read the port number from the command line; it is supplied implicitly by inetd .

Lines 10 “13: Recover the listening socket We recover the listening socket from STDIN . First we check that we are indeed running under inetd by testing that STDIN is a socket using the -S file test. If STDIN passes this test, we turn it back into an IO::Socket object by calling IO::Socket's new_from_fd() method. This method, inherited from IO::Handle, is similar to fdopen() except that instead of reopening an existing handle on the specified filehandle, it creates a new handle that is a copy of the old one. In this case, we create a new IO::Socket object that is a copy of STDIN , opened for reading and writing with the "+<" mode.

Lines 15 “21: Call accept() with a timeout We now enter a standard accept() loop, except that the call to accept() is wrapped in an eval{} block. Within the eval , we create a local ALRM signal handler that calls die() , and use alarm() to set a timer that will go off after $timeout minutes have expired . We then call the listen socket's accept() method. If an incoming connection is received before the timeout expires , then the result from the eval{} block is the connected socket. Otherwise, the ALARM signal handler is called, and the eval{} block is aborted, returning an undefined value. In the latter case, we call exit() , terminating the whole server. Otherwise, we call alarm(0) to cancel the timeout.

Lines 22 “43: The remainder of the server is unchanged. We also include the Chatbot::Eliza::_testquit() workaround in order to avoid problems when the user closes the connection unexpectedly.

When I first wrote this program, I thought that I could simply use IO::Socket's built-in timeout mechanism, rather than roll my own ALRM -based timeout. However, there turned out to be a problem. With the built-in timeout activated, accept() returned undef both when the legitimate timeout occurred and when it was interrupted by the CHLD signal that accompanies every child process's termination. After some trial and error, I decided there was no easy way to distinguish between the two events, and went with the technique shown here.

inetd can also be used to launch UDP applications. In this case, when the program is launched, it finds STDIN already opened on an appropriate UDP socket. recv() and send() can then be used to communicate across the socket in the normal way. See Chapters 18 and 19 for more details.


   
Top


Network Programming with Perl
Network Programming with Perl
ISBN: 0201615711
EAN: 2147483647
Year: 2000
Pages: 173

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net