Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 11 Netzwerkprogrammierung
  gp 11.1 Einführung
  gp 11.2 Aufbau von Netzwerken
    gp 11.2.1 ISO/OSI und TCP/IP – Referenzmodell
    gp 11.2.2 Das World Wide Web (Internet)
  gp 11.3 TCP/IP – Aufbau und Struktur
    gp 11.3.1 Netzwerkschicht (Datenübertragung)
    gp 11.3.2 Internetschicht
    gp 11.3.3 Transportschicht (TCP, UDP)
    gp 11.3.4 Anwendungsschicht
  gp 11.4 TCP Socket
  gp 11.5 Kommunikationsmodell
  gp 11.6 Grundlegende Funktionen zum Zugriff auf die Socket-Schnittstelle
    gp 11.6.1 Ein Socket anlegen – socket()
    gp 11.6.2 Verbindungsaufbau – connect()
    gp 11.6.3 Socket mit einer Adresse verknüpfen – bind()
    gp 11.6.4 Auf Verbindungen warten – listen() und accept()
    gp 11.6.5 Senden und Empfangen von Daten (1) – write() und read()
    gp 11.6.6 Senden und Empfangen von Daten (2) – send() und recv()
    gp 11.6.7 Verbindung schließen – close()
  gp 11.7 Aufbau eines Clientprogramms
    gp 11.7.1 Zusammenfassung: Clientanwendung und Quellcode
  gp 11.8 Aufbau des Serverprogramms
    gp 11.8.1 Zusammenfassung: Serveranwendung und Quellcode
  gp 11.9 IP-Adressen konvertieren, manipulieren und extrahieren
    gp 11.9.1 inet_aton(), inet_pton() und inet_addr()
    gp 11.9.2 inet_ntoa() und inet_ntop()
    gp 11.9.3 inet_network()
    gp 11.9.4 inet_netof()
    gp 11.9.5 inet_lnaof()
    gp 11.9.6 inet_makeaddr()
  gp 11.10 Namen und IP-Adressen umwandeln
    gp 11.10.1 Name-Server
    gp 11.10.2 Informationen zum Rechner im Netz – gethostbyname und gethostbyaddr
    gp 11.10.3 Service-Informationen – getservbyname() und getservbyport()
  gp 11.11 Der Puffer
  gp 11.12 Standard-E/A-Funktionen verwenden
    gp 11.12.1 Pufferung von Standard-E/A-Funktionen
  gp 11.13 Parallele Server
  gp 11.14 Syncrones Multiplexing – select()
  gp 11.15 POSIX-Threads und Netzwerkprogrammierung
  gp 11.16 Optionen für Sockets setzen bzw. erfragen
    gp 11.16.1 setsockopt()
    gp 11.16.2 getsockopt()
    gp 11.16.3 Socket-Optionen
  gp 11.17 UDP
    gp 11.17.1 Clientanwendung
    gp 11.17.2 Serveranwendung
    gp 11.17.3 recvfrom() und sendto()
    gp 11.17.4 bind() verwenden oder weglassen
  gp 11.18 UNIX-Domain-Sockets (IPC)
    gp 11.18.1 Die Adressstruktur von UNIX-Domain-Sockets
    gp 11.18.2 Lokale Sockets erzeugen – socketpair()
  gp 11.19 Multicast-Socket
    gp 11.19.1 Anwendungsgebiete von Multicast-Verbindungen
  gp 11.20 Nicht blockierende I/O-Sockets
  gp 11.21 Etwas zu Streams und TLI, Raw Socket, XTI
    gp 11.21.1 Raw Socket
    gp 11.21.2 TLI und XTI
    gp 11.21.3 RPC (Remote Procedure Call)
  gp 11.22 IPv4 und IPv6
    gp 11.22.1 IPv6 – ein wenig genauer
  gp 11.23 Netzwerksoftware nach IPv6 portieren
    gp 11.23.1 Konstanten
    gp 11.23.2 Strukturen
    gp 11.23.3 Funktionen
  gp 11.24 Sicherheit und Verschlüsselung


Rheinwerk Computing

11.8 Aufbau des Serverprogramms  downtop

Jetzt benötigen Sie die Verbindung zur Gegenseite, zum Server. Der Unterschied zum Clientprogramm besteht im Wesentlichen nur darin, wie eine Verbindung zustande kommt. Daher wird ein Server immer in einen Wartezustand versetzt, um Verbindungswünsche von Clientprogrammen entgegenzunehmen. Die Datenübertragung hingegen verläuft genauso wie beim Client.

Das Serverprogramm muss auch zunächst herausfinden, an welchem Port es auf Verbindungswünsche warten soll. In unserem Beispiel wird die Portnummer erst fest im Quellcode einkompiliert. Ansonsten könnten Sie hierfür die Funktion getservbyname() zur Ermittlung eines Ports mittels dessen Namen verwenden (mehr dazu später).

Zuerst müssen Sie auch beim Server ein Socket mit socket() erzeugen, um überhaupt mit den Clientprogrammen kommunizieren zu können. (Oder: Was nützt der Rasierapparat ohne eine Stromsteckdose?)

Anschließend ist es an der Zeit, dem Betriebssystem mit der Funktion bind() mitzuteilen, welchem Port das Socket zugewiesen werden soll. Wenn hierbei ein Datenpaket empfangen wird, weiß das System anhand der Portnummer, für welches Socket das Paket gedacht ist. Sie können sich die Portnummer hierbei als Durchwahl einer Telefonnummer vorstellen, wie dies bei größeren Firmen der Fall ist. Hat eine Firma z. B. die Nummer 12345 und Herr Huber die Nummer 134, dann wäre die komplette Nummer mit Durchwahl 12345–134, und Herr Huber ist am Apparat. Umgesetzt auf die Socket-Programmierung ist die IP-Adresse die Nummer zur Firma und die Durchwahl die Nummer des Ports. Zurück zum Thema. Die Aufgabe von bind() ist es, das neu erzeugte Socket mit dem Port zu binden. Mit der Funktion bind() geben Sie die Endpunktadresse (IP-Adresse: Portnummer) an, unter der das Socket erreichbar sein soll. Das heißt, dass ein Socket, das mit bind(192.168.201.1, 8000) gebunden wurde, NIE aus dem Internet ansprechbar ist, während es mit bind(134.76.13.21, 8000) NIE aus dem internen Netzwerk erreichbar ist. Aber mit bind(INADDR_ANY, 8000) ist es überall erreichbar. An welche Adresse richtet sich dann die Anfrage? Für solch einen Fall (was meistens immer der Fall ist) wird die Konstante INADDR_ANY anstelle einer festen IP-Adresse verwendet. Dadurch wird der Server angewiesen, Verbindungen für jede IP-Adresse anzunehmen (die dem Server auch gehört).

struct sockaddr_in address;
...
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons (15000);
if (bind ( create_socket,
           (struct sockaddr *) &address,
           sizeof (address)) == 0)
   printf ("Binding Socket erfolgreich\n");

Jetzt, da der Server bereit ist, auf eingehende Verbindungswünsche zu reagieren, können Sie mit der Funktion listen() eine Warteschlange einrichten, um mehreren Verbindungswünschen Einlass zu gewähren. Anschließend wird die Funktion accept() aufgerufen. Die Adressinformationen über den Client befinden sich danach im zweiten Parameter der Funktion accept(). Diese benötigen Sie, damit Sie überhaupt wissen, mit wem Sie es zu tun haben. Wichtig hierbei ist, dass die Verbindung von nun an über das neu erstellte Socket, das accept() als Rückgabewert bei Erfolg liefert, abgewickelt wird. Das »alte« Socket steht weiterhin für die weiteren Verbindungsaufbauwünsche zur Verfügung. Allerdings können Sie mit der jetzigen Codeform nur eine Verbindung zur gleichen Zeit bearbeiten. Wie Sie theoretisch mehrere Verbindungen parallel abarbeiten können, erfahren Sie ein paar Seiten später (geht auch mit einer CPU). Gewöhnlich werden Sie auch wollen, dass der Server sich nach einem Durchlauf nicht beendet. Daher sollten Sie hierfür accept() meistens in eine Endlosschleife verpacken, womit der Server dauerhaft Verbindungswünsche eingehen kann.


Rheinwerk Computing

11.8.1 Zusammenfassung: Serveranwendung und Quellcode  toptop

Zusammengefasst besteht eine Serveranwendung gewöhnlich aus den folgenden Einzelteilen:

gp  einen Socket erzeugen – socket()
gp  den eigenen Port festlegen – bind()
gp  auf Verbindungswünsche warten – listen()
gp  Verbindung annehmen – accept()
gp  Kommunikation – was auch immer (Daten empfangen oder versenden)
gp  Verbindung schließen – close()

Hierzu der Quellcode des Servers:

/* server.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd. h.>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUF 1024
int main (void) {
  int create_socket, new_socket;
  socklen_t addrlen;
  char *buffer = malloc (BUF);
  ssize_t size;
  struct sockaddr_in address;
  const int y = 1;
  printf ("\e[2J");
  if ((create_socket=socket (AF_INET, SOCK_STREAM, 0)) > 0)
    printf ("Socket wurde angelegt\n");
  setsockopt( create_socket, SOL_SOCKET,
              SO_REUSEADDR, &y, sizeof(int));
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_port = htons (15000);
  if (bind ( create_socket,
             (struct sockaddr *) &address,
             sizeof (address)) != 0) {
    printf( "Der Port ist nicht frei – belegt!\n");
  }
  listen (create_socket, 5);
  addrlen = sizeof (struct sockaddr_in);
  while (1) {
     new_socket = accept ( create_socket,
                           (struct sockaddr *) &address,
                           &addrlen );
     if (new_socket > 0)
      printf ("Ein Client (%s) ist verbunden ...\n",
         inet_ntoa (address.sin_addr));
     do {
        printf ("Nachricht zum Versenden: ");
        fgets (buffer, BUF, stdin);
        send (new_socket, buffer, strlen (buffer), 0);
        size = recv (new_socket, buffer, BUF-1, 0);
        if( size > 0)
           buffer[size] = '\0';
        printf ("Nachricht empfangen: %s\n", buffer);
     } while (strcmp (buffer, "quit\n") != 0);
     close (new_socket);
  }
  close (create_socket);
  return EXIT_SUCCESS;
}

Jetzt wird es Zeit, das Client-Server-Beispiel zu testen. Hier die Client-Server-Anwendung bei der Ausführung. Da ich davon ausgehe, dass Sie dieses Beispiel auf dem lokalen Rechner testen wollen, sollten Sie den Client mit der IP-Adresse Ihres Heimrechners starten – was gewöhnlich 127.0.0.1 (localhost) ist. Falls Sie die Serveranwendung allerdings woanders eingerichtet haben als die Clientanwendung, so geben Sie hierfür eben die entsprechende IP-Adresse des Servers an. Sie unterhalten sich im Beispiel quasi über dieselbe IP-Adresse (127.0.0.1).

[tty1]$ gcc -o client client.c
[tty1]$ gcc -o server server.c
[tty1] $ ./server
Socket wurde angelegt
---[Terminal wechseln]---
[tty2]$ ./client 127.0.0.1
Socket wurde angelegt
Verbindung mit dem Server (127.0.0.1) hergestellt
---[tty1]---
Socket wurde angelegt
Der Client 127.0.0.1 ist verbunden ...
Nachricht zum Versenden: Hallo Client
---[tty2]---
Socket wurde angelegt
Verbindung mit dem Server (127.0.0.1) hergestellt
Nachricht erhalten: Hallo Client
Nachricht zum Versenden: Hallo Server
---[tty1]---
Socket wurde angelegt
Der Client 127.0.0.1 ist verbunden ...
Nachricht zum Versenden: Hallo Client
Nachricht empfangen: Hallo Server
Nachricht zum Versenden: Beende dich mit quit
---[tty2]---
Socket wurde angelegt
Verbindung mit dem Server (127.0.0.1) hergestellt
Nachricht erhalten: Hallo Client
Nachricht zum Versenden: Hallo Server
Nachricht erhalten: Beende dich mit quit
Nachricht zum Versenden: quit
$
---[tty1]---
Socket wurde angelegt
Der Client 127.0.0.1 ist verbunden ...
Nachricht zum Versenden: Hallo Client
Nachricht empfangen: Hallo Server
Nachricht zum Versenden: Beende dich mit quit
Nachricht empfangen: quit
[tty3] $ ./client 127.0.0.1
Socket wurde angelegt
Die Verbindung wurde "accepted" mit dem Server 127.0.0.1
---[tty1]---
...
Nachricht zum Versenden: Beende dich mit quit
Nachricht empfangen: quit
Der Client 127.0.0.1 ist verbunden ...
Nachricht zum Versenden:
...

Hinweis   Mit der Funktion setsockopt() richten Sie das Socket so ein, dass mehrere Prozesse (Clients) denselben Port teilen – sprich: Mehrere Clients können innerhalb kürzester Zeit mit dem Server in Verbindung treten. Außerdem lösen Sie damit auch das Problem, dass der Server beim Neustart seinen lokalen Port erst nach zwei Minuten Wartezeit wieder benutzen kann. Dies werden Sie in fast allen Beispielen wieder finden. Mehr zur Funktion setsockopt() finden Sie in einem späteren Abschnitt (10.15) wieder.


Sie haben hier praktisch eine einfache Client-Server-Anwendung erstellt, womit Sie einfachen Text miteinander austauschen können. Natürlich habe ich es mir bei diesem Beispiel sehr leicht gemacht. Für solch ein Beispiel hätten Sie sich kein Buch kaufen müssen – aber zur Einführung ist dies genau das Richtige. Daher soll jetzt auf den folgenden Seiten noch tiefer in die Materie vorgedrungen werden. Jetzt haben Sie zwar eine Menge Sitzfleisch für das Kapitel aufwenden müssen – dafür haben Sie sich allerdings die Grundlagen zur Netzwerkprogrammierung gelegt. Daher wird auf den folgenden Seiten der Praxis-Anteil wieder gesteigert.


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 11.5    Grundlegendes Client-Server-Prinzip bei den TCP-Sockets


 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern