Netzwerkprogrammierung

Martin Kompf

Das Erstellen von Netzwerkprogrammen, die per TCP/IP miteinander kommunizieren, gestaltet sich mittels C und der Socketschnittstelle relativ einfach. Es wird ein Netzwerkclient vorgestellt, der per HTTP Seiten von einem Webserver laden kann.

Die Begriffe Client und Server gehören zu den am meisten gebrauchten, aber wohl auch oft missverstandenen Begriffen der Informationstechnik. Der Softwareentwickler versteht unter «Server» mitnichten eine teuere, grau gestrichene Blechkiste, die im klimatisierten «Serverraum» ihr Dasein fristet, sondern vielmehr ein Stück Software, welches einen bestimmten, genau festgelegten Dienst oder Service im Netz anbietet. Ein «Client» ist dementsprechend auch ein Stück Software, welches einen solchen Service benutzt.

Dazu muss zwischen Client und Server eine Netzwerkverbindung bestehen. Die Kommunkation über das Netzwerk folgt dabei verschiedenen Protokollen, deren wichtigstes und bekanntes TCP/IP ist. Genauer gesagt handelt es sich dabei sogar um zwei Protokolle: IP (Internet Protocol) regelt dabei die Adressierung und den Versand von Paketen, während TCP (Transmission Control Protocol) dem Anwender die stabile und unterbrechungsfreie Übertragung seiner Daten garantiert.

Es gibt mittlerweile einige Programmierschnittstellen, die den Zugriff auf TCP/IP für ein C Programm erlauben. Die bekannteste ist die Socketschnittstelle, die unter Unix und Windows (als «Winsock»-API) zur Verfügung steht. Ihr großer Vorteil: Einmal richtig initialisiert, stellt sich eine Netzwerkverbindung für den Programmierer als ein Socket dar, welches Schreib- und Leseoperationen in der selben Art und Weise wie eine geöffnete Datei erlaubt.

Das folgende Programm (Download httpget.c) verdeutlicht die einzelnen Schritte, die ein Client vornehmen muss, um per TCP/IP mit einem Server zu kommunizieren. Um das ganze anschaulicher zu gestalten, implementiert dieses Programm einen minimalen Webclient, der in der Lage ist, per HTTP Anfragen an einen Webserver zu richten und dessen Antwort auf Standardausgabe auszugeben. Die Kommentare im Programm sprechen für sich, eine Übersetzung des Codes in ein lauffähiges Executable ist unter Windows zum Beispiel mit dem freien Borland Compiler per

bcc32 httpget.c

oder dem MinGW Compiler durch


gcc -o httpget.exe httpget.c -lwsock32

und unter Linux mit dem GNU-Compiler via

gcc -o httpget httpget.c

möglich.

/* httpget.c
 * Demoprogramm zur Programmierung von Netzwerkclients
 * Es wird ein GET request via http an einen Webserver 
 * gesendet und das Ergebnis auf der Konsole ausgegeben. */
#include <stdio.h>
#include <errno.h>

#ifdef _WIN32
/* Headerfiles für Windows */
#include <winsock.h>
#include <io.h>
#define in_addr_t unsigned long

#else
/* Headerfiles für Unix/Linux */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet>
#include <unistd.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#define closesocket(s) close(s)
#endif

/* http requests werden normalerweise auf Port 80 
 * vom Server entgegengenommen */
#define HTTP_PORT 80

/* Verwende HTTP Version 1.1 statt 1.0 */
#define HTTP_VER_11

/****************** MAIN *********************/
int main( int argc, char **argv)
{
    struct sockaddr_in server;
    struct hostent *host_info;
    in_addr_t addr;
    int sock;
    char buffer[8192];
    int count;
    
    
#ifdef _WIN32  
    /* Initialisiere TCP für Windows ("winsock") */
    short wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD (1, 1);
    if (WSAStartup (wVersionRequested, &wsaData) != 0) {
        fprintf( stderr, "Failed to init windows sockets\n");
        exit(1);
    }
#endif

    /* Sind die erforderlichen Kommandozeilenargumente vorhanden? */
    if (argc != 3) {
        fprintf( stderr, "usage: httpget server file\n");
        exit(1);
    }

    /* Erzeuge das Socket */
    sock = socket( PF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror( "failed to create socket");
        exit(1);
    }

    /* Erzeuge die Socketadresse des Servers 
     * Sie besteht aus Typ, IP-Adresse und Portnummer */
    memset( &server, 0, sizeof (server));
    if ((addr = inet_addr( argv[1])) != INADDR_NONE) {
        /* argv[1] ist eine numerische IP-Adresse */
        memcpy( (char *)&server.sin_addr, &addr, sizeof(addr));
    }
    else {
        /* Wandle den Servernamen in eine IP-Adresse um */
        host_info = gethostbyname( argv[1]);
        if (NULL == host_info) {
            fprintf( stderr, "unknown server: %s\n", argv[1]);
            exit(1);
        }
        memcpy( (char *)&server.sin_addr, host_info->h_addr, host_info->h_length);
    }

    server.sin_family = AF_INET;
    server.sin_port = htons( HTTP_PORT);

    /* Baue die Verbindung zum Server auf */
    if ( connect( sock, (struct sockaddr*)&server, sizeof( server)) < 0) {
        perror( "can't connect to server");
        exit(1);
    }

    /* Erzeuge und sende den http GET request */
#ifdef HTTP_VER_11
    /* HTTP 1.1: Request enthält Pfad, Hostname und Hinweis, dass der 
     * Server die Verbindung nach Abarbeitung des Requests schließen soll */
    sprintf( buffer, "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", argv[2], argv[1]);
#else
    /* HTTP 1.0: Request enthält nur den Pfad zur Datei */
    sprintf( buffer, "GET %s HTTP/1.0\r\n\r\n", argv[2]);
#endif
    send( sock, buffer, strlen( buffer), 0);

    /* Hole die Serverantwort und gib sie auf Konsole aus */
    do {
        count = recv( sock, buffer, sizeof(buffer), 0);
        write( 1, buffer, count);
    }
    while (count > 0);

    /* Schließe Verbindung und Socket */
    closesocket( sock);
    return count;
}

Nach erfolgreichem Übersetzen und bei bestehender Verbindung zum Internet kann das Programm zum Beispiel mittels

httpget www.apache.org /index.html

die Einstiegsseite des Apache-Webservers laden. Es wird natürlich nur der HTML-Quelltext dieser Seite angezeigt. Genauer gesagt, es wird die komplette Serverantwort inklusive des HTTP-Headers ausgegeben! Aus diesem lassen sich interessante Informationen gewinnen, die dem Benutzer eines gewöhnlichen Browsers vorenthalten werden. So sagt uns in diesem Fall die Serverantwort, dass Apache offensichtlich den Apache 2 unter Unix als Webserver benutzt...