Server mit fork

Durch die Verwendung von fork() kann man einen Kindprozess erzeugen, der sich dann z.B. mit dem Client beschäftigen kann. Das englische Wort fork bezeichnet eine Gabelung, was hier ziemlich genau zutrifft: der aktuelle Prozeß wird dupliziert, mit den meisten seiner Eigenschaften (insbesondere Code, Speicher, Stelle der Programmausführung, offene Dateien, Rechte, etc.). Die Funktion fork() wird einmal aufgerufen, und kehrt zweimal zurück: der eine Aufrufer, der Elternprozess, erhält die PID des neuen Kindprozesses, der Kindprozess erhält 0 (da er mit getpid(2) und getppid(2) alle nötigen Informationen erfragen kann).

Wichtig im Zusammenhang mit Prozessen ist das Vermeiden von Zombies. Ein Zombie ist ein totes Kind, dessen Elternprozeß sich nicht um sein Ableben kümmert. Technischer gesprochen: der Kernel hält solange Informationen über den Kindprozeß (wie bspw. Exit-Status) bereit, bis sie vom Elternprozeß abgeholt werden. Solange bleibt auch ein Eintrag in der Prozeßtabelle vorhanden, weshalb man mit dem Kommando 'ps' auch Zombies sehen kann.

Um Zombies zu vermeiden, müssen die Eltern mit wait(2) oder Verwandten diese Informationen abholen. Damit sie wissen, wann das nötig ist, werden sie, wann immer ein Kind stirbt, mit dem Signal SIGCHLD benachrichtigt. Für das Signalhandling bemühen wir die Funktion sigaction(2) (welche signal(3) ablöst und ersetzen sollte).

Gern gemachte Fehler: der Kindprozeß sollte sich in diesem Fall beenden, nachdem er fertig ist. Das passiert hier in main() via exit(). Passiert das nicht, so würde in der Endlosschleife nach jedem Client-Besuch ein weiteres Kind warten. Der zweite Fehler ist etwas subtiler: da auch die Sockets beim fork dupliziert werden, muß der Elternprozess ebenfalls close(2) aufrufen, da sonst der Client "kleben" bleiben würde, wenn sich das Kind beendet (und der Socket somit noch offen gehalten wird).

Hier nun der ersehnte Beispielcode:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

int handle_client(int sock)
{
    char *msg = "Hello, World!\r\n";

    send(sock, msg, strlen(msg), 0);

    return 0;
}

void sigchld_handler(int n)
{
    int status;
    pid_t pid;

    pid = wait(&status);

    printf("%i exited with %i\n", pid, WEXITSTATUS(status));
}

int main(void)
{
    int sock, client;
    pid_t pid;
    struct sockaddr_in addr;
    struct sigaction sa;

    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket() failed");
        return 1;
    }

    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234);

    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
        perror("bind() failed");
        return 2;
    }

    if (listen(sock, 3) < 0)
    {
        perror("listen() failed");
        return 3;
    }

    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigchld_handler;
    sigaction(SIGCHLD, &sa, NULL);

    for(;;)
    {
        client = accept(sock, NULL, 0);
        if (client < 0)
        {
            if (errno != EINTR)
                perror("accept() failed");
            continue;
        }

        pid = fork();

        if (pid < 0)
        {
            perror("fork() failed");
            return 5;
        }

        if (pid == 0)
            exit(handle_client(client));

        printf("Client has PID %i\n", pid);

        close(client);
    }

    return 0;
}