Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

TCP-Drei-Wege-Handshake mit Event-Driven Server: Ein Leitfaden für das TextFilter-Projekt

Lerne, wie du einen TCP-Client und Server mit Drei-Wege-Handshake und event-driven Techniken implementierst. Perfekt für dein TextFilter-Projekt in der Netzwerkprogrammierung.

TCP Drei-Wege-Handshake Event-Driven Server Single-Threaded Server Concurrent Server TCP Client Server C TextFilter Projekt Netzwerkprogrammierung C select() Server fflush stdout HELLO X Y Z Handshake Socket Programmierung C Netzwerkprojekt KI Chat Verbindungen Discord Server Architektur Asynchrone Kommunikation TCP Handshake Fehler

Einführung in den TCP-Drei-Wege-Handshake

Der TCP-Drei-Wege-Handshake ist ein fundamentales Protokoll, das die zuverlässige Verbindung zwischen zwei Netzwerkteilnehmern herstellt. In diesem Tutorial implementierst du einen TCP-Client und Server, der genau diesen Handshake durchführt – eine Kernkompetenz für dein TextFilter-Projekt. Wir nutzen dabei event-driven Server, um mehrere Clients gleichzeitig zu bedienen, ohne Threads zu verwenden. Das ist besonders relevant, wenn du verstehen willst, wie moderne Apps wie Discord oder WhatsApp gleichzeitig Tausende von Verbindungen verwalten.

Projektaufbau: Client und Single-Threaded Server

Dein Projekt besteht aus drei Teilen: einem wiederverwendbaren Client, einem synchronen Server und einem asynchronen Server. In diesem Fokus liegt auf dem synchronen Teil (3a und 3b). Der Drei-Wege-Handshake funktioniert wie ein Gespräch zwischen zwei Personen: Person A sagt 'HELLO X', Person B antwortet mit 'HELLO Y' (wobei Y = X+1), und A bestätigt mit 'HELLO Z' (Z = Y+1). Wenn die Zahlen nicht stimmen, gibt es einen Fehler. Das ist ähnlich wie beim Fortnite-Battle-Pass: Du erhältst eine Quest (X), erledigst sie (Y) und kassierst die Belohnung (Z). Nur wenn die Schritte korrekt sind, funktioniert das System.

Schritt 1: Der Client sendet 'HELLO X'

Der Client öffnet eine TCP-Verbindung zum Server und sendet eine Nachricht im Format HELLO X, wobei X eine ganze Zahl ist (z.B. 1). Verwende dazu send() oder write(). Der Server muss diese Nachricht empfangen und parsen. Ein typischer Fehler ist, dass das fflush(stdout) nach dem Drucken vergessen wird – das führt zu gepufferten Ausgaben, die der Autograder nicht sieht.

// Client: Sende HELLO 1
char buffer[256];
snprintf(buffer, sizeof(buffer), "HELLO %d", 1);
send(sockfd, buffer, strlen(buffer), 0);

Schritt 2: Server antwortet mit 'HELLO Y' (Y = X+1)

Der Server liest die Nachricht, extrahiert X und berechnet Y = X+1. Dann sendet er HELLO Y zurück. Achte darauf, dass der Server die Nachricht vollständig empfängt – TCP ist ein Stream-Protokoll, daher kann eine Nachricht in mehreren Paketen ankommen. Verwende eine Schleife, bis du das gesamte 'HELLO X' erhalten hast.

// Server: Empfange und antworte
char buffer[256];
int n = recv(client_fd, buffer, sizeof(buffer)-1, 0);
buffer[n] = '\0';
int x;
sscanf(buffer, "HELLO %d", &x);
int y = x + 1;
snprintf(buffer, sizeof(buffer), "HELLO %d", y);
send(client_fd, buffer, strlen(buffer), 0);
printf("Received: HELLO %d, Sent: HELLO %d\n", x, y);
fflush(stdout);

Schritt 3: Client sendet 'HELLO Z' (Z = Y+1) und schließt

Der Client empfängt die Antwort, extrahiert Y, berechnet Z = Y+1 und sendet HELLO Z. Danach schließt er die Verbindung mit close(). Das ist wie bei einer KI-gestützten Chat-App: Du sendest eine Nachricht, bekommst eine Antwort und bestätigst den Erhalt.

// Client: Empfange Y und sende Z
char buffer[256];
int n = recv(sockfd, buffer, sizeof(buffer)-1, 0);
buffer[n] = '\0';
int y;
sscanf(buffer, "HELLO %d", &y);
int z = y + 1;
snprintf(buffer, sizeof(buffer), "HELLO %d", z);
send(sockfd, buffer, strlen(buffer), 0);
close(sockfd);

Schritt 4: Server prüft Z und schließt

Der Server empfängt HELLO Z, prüft ob Z == Y+1. Wenn nicht, gibt er 'ERROR' aus. Dann schließt er die Client-Verbindung. Wichtig: Nach jeder Ausgabe ein fflush(stdout).

// Server: Prüfe Z
char buffer[256];
int n = recv(client_fd, buffer, sizeof(buffer)-1, 0);
buffer[n] = '\0';
int z;
sscanf(buffer, "HELLO %d", &z);
if (z != y+1) {
    printf("ERROR\n");
    fflush(stdout);
}
close(client_fd);

Event-Driven Server: Single-Threaded und Concurrent

Ein event-driven Server verwendet select(), poll() oder epoll(), um mehrere Client-Sockets gleichzeitig zu überwachen, ohne Threads zu spawnen. Das ist ressourcenschonend und eignet sich für Concurrent-Server mit vielen Verbindungen. Stell dir vor, du bist ein Barkeeper, der mehrere Bestellungen gleichzeitig aufnimmt, ohne dass du für jeden Gast einen eigenen Kellner einstellst. Genauso arbeitet ein Single-Threaded-Server mit select().

Implementierung mit select()

Zunächst erstellst du einen Listen-Socket und fügst ihn zu einer Socket-Menge (fd_set) hinzu. In einer Schleife rufst du select() auf, das blockiert, bis einer der Sockets bereit ist. Wenn der Listen-Socket bereit ist, nimmst du eine neue Verbindung an und fügst den Client-Socket zur Menge hinzu. Wenn ein Client-Socket bereit ist, liest du Daten und führst den Handshake aus. Nach Abschluss entfernst du den Socket aus der Menge und schließt ihn.

// Event-Driven Server mit select()
fd_set master_set, read_fds;
FD_ZERO(&master_set);
FD_SET(listen_fd, &master_set);
int max_fd = listen_fd;

while (1) {
    read_fds = master_set;
    if (select(max_fd+1, &read_fds, NULL, NULL, NULL) == -1) {
        perror("select");
        exit(1);
    }
    for (int i = 0; i <= max_fd; i++) {
        if (FD_ISSET(i, &read_fds)) {
            if (i == listen_fd) {
                // Neue Verbindung
                struct sockaddr_in client_addr;
                socklen_t addr_len = sizeof(client_addr);
                int new_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
                FD_SET(new_fd, &master_set);
                if (new_fd > max_fd) max_fd = new_fd;
                printf("Neue Verbindung von %s\n", inet_ntoa(client_addr.sin_addr));
            } else {
                // Daten von Client
                char buffer[256];
                int n = recv(i, buffer, sizeof(buffer)-1, 0);
                if (n <= 0) {
                    // Verbindung geschlossen oder Fehler
                    close(i);
                    FD_CLR(i, &master_set);
                } else {
                    // Handshake-Logik hier
                    // ...
                }
            }
        }
    }
}

Häufige Fehler und Tipps

  • Vergessen von fflush(stdout): Der Autograder erwartet sofortige Ausgabe. Nach jedem printf ein fflush(stdout) einfügen.
  • Unvollständige Nachrichten: TCP liefert einen Bytestrom. Verwende eine Schleife, um die gesamte Nachricht zu empfangen (z.B. bis ein '\n' oder eine bestimmte Länge erreicht ist).
  • Port bereits belegt: Verwende SO_REUSEADDR auf dem Server-Socket, um den Port sofort wiederverwenden zu können.
  • Compile-Warnings: Kompiliere mit -Wall -Wextra und behebe alle Warnings, da der Autograder sonst fehlschlägt.

Trend-Beispiel: Wie ChatGPT Verbindungen verwaltet

Wenn du ChatGPT nutzt, sendest du eine Nachricht (HELLO X), der Server antwortet (HELLO Y) und du erhältst die Antwort (HELLO Z). Auch hier wird ein event-driven Server eingesetzt, um Millionen von Anfragen gleichzeitig zu bearbeiten. Dein TextFilter-Projekt bildet die Grundlage für solche Systeme – verstehst du den Drei-Wege-Handshake, verstehst du die Basis von Netzwerkkommunikation.

Zusammenfassung

Du hast gelernt, wie ein TCP-Drei-Wege-Handshake funktioniert und wie du ihn in C implementierst. Mit einem event-driven Server kannst du mehrere Clients gleichzeitig bedienen, ohne Threads zu benötigen. Dieses Wissen ist nicht nur für dein TextFilter-Projekt essenziell, sondern auch für viele reale Anwendungen wie Webserver, Chatdienste oder IoT-Geräte. Viel Erfolg bei der Implementierung!