next up previous contents
Next: Reimplementing server functionality Up: Implementation Previous: The dual I/O problem   Contents


Dealing with different data sources

The first step was to get ProtoWrap to work with one model of communication. The first path taken was using socket communication on both ends: The wrapper became a TCP daemon at startup, and when a connection is recieved, a new TCP connection is created to the server. Although this will be the most common setup, it is very important to provide at least two other basic features: The ability to be started from inetd (in order to be able to bind to low ports without requiring root privileges, avoiding also being always loaded in RAM for seldom-used services) and the ability to invoke the server locally, handling I/O via the STDIN/STDOUT filehandles (allowing us not to open a TCP connection, and effectlively avoiding any external user from getting to the actual server).

Achieving this was a challenge: In order not to duplicate the code that handles communication, we will handle two filehandles per connection, one to be used for reading ($client_rd and $server_rd) and one for writing ($client_wr and $server_wr). If we are working with sockets (a real TCP connection), the read and write filehandles will point to the same actual object, but they need to be different when dealing with any other connection type.

Process spawining behavior must also adapt to different invocation styles: When the wrapper is invoked standalone, as soon as it is started it forks to detach itself from the main program. This is definitively not wanted when called from inetd. Programs called from inetd do not close the connection by closing a socket --they close the connection when their execution finishes. If the program forked, it would never close the connection, and the client could be left hanging forever. Thus, the startServer function appears as follows:


sub startServer {
    my $self = shift;
    my ($error,$child);
    $error=0;
    if ($self->{standalone} == 1) {
        $self->{listSock} = IO::Socket::INET->new(
            LocalPort => $self->{listenPort},
            Type      => SOCK_STREAM,
            Reuse     => 1,
            Listen    => 10) 
            or $self->log(0,
            "Couldn't be a TCP server on port $self->{listenPort}") && 
               ($error = 1);
        return undef if $error;
        $self->log(0,"Can't fork to accept incoming connections!: $@ $!") 
               if (!defined($child=fork()));
        if ($child == 0) {
            # Child process - take care of the connection
            $self->getConn();
        } else {
            # Parent process - return the child process' PID
            $self->{pid} = $child;
        }
    } else {
        $self->getConn();
    }
}

In the main program loop a fork() must also be avoided when called from inetd. The wrapper will not wait for an incoming connection -- The connection is already there when the program is invoked. If a fork() happened as it usually would, the server would be called over and over again, exhausting the computer's resources. To avoid this, we fool the program into thinking it has already been forked and the child process is now being executed:


if ($self->{standalone} == 1) {
    # Standalone mode - Every recieved connection should get
    # its own server
    $self->log(0,"Can't fork to handle incoming client connection!: 
        $@ $!") if (!defined ($child=fork()));
} else {
    # inetd mode - The forking is done by inetd, so we act as
    # the child process
    $child = 0;
}


next up previous contents
Next: Reimplementing server functionality Up: Implementation Previous: The dual I/O problem   Contents
Gunnar Wolf
2001-03-12