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.15 POSIX-Threads und Netzwerkprogrammierung  toptop

Wenn sich fork() dazu eignet, einen parallelen Server zu entwickeln, dann müssten ja die Threads als Leichtgewichte sensationell im Vergleich zu den normalen Prozessen sein. Das Thema »Threads« wurde ja bereits in Kapitel 9 behandelt. Mit Threads würde man sich praktisch eine Menge Hauptspeicher sparen können, da ja das »aufwändige« Kopieren vom Eltern- in den Kindprozess entfallen würde und man auf Interprozesskommunikationen zum Austausch von Informationen zwischen Eltern- und Kindprozess verzichten könnte. Die Threads können ja als Leichtgewichtprozesse bis zu 100 Mal schneller sein als herkömmliche neue Prozesse, die mit fork() erzeugt wurden.

Bei all dem Hurra über Threads in der Netzwerkprogrammierung muss allerdings einiges beachtet werden, wenn man die Threads tatsächlich zur Socket-Programmierung verwenden will. Da wäre zum Beispiel die Anzahl der gleichzeitigen Threads. Wenn Threads also bei einem Server eingesetzt werden sollen, der mehrere tausend Clients bedienen soll, dann sind sie schon nicht mehr dafür geeignet.


Ein interessanter Vorschlag   Man greift zur Extremlösung (die aber auch wunderbar funktioniert): Man forkt den Hauptprozess (Elternprozess) direkt nach dem listen() mehrmals, und jeder der Subprozesse (Kindprozess) erstellt dann ein paar Threads. Somit kommt man auch um das Limit gleichzeitiger Threads pro Prozess ...


Des Weiteren sind die Threads in der Netzwerkprogrammierung noch verhältnismäßig »jung«, so dass in puncto Zuverlässigkeit und Erfahrung noch recht wenige Kenntnisse vorhanden sind.


Hinweis   Thread-sichere Versionen von Funktionen enden gewöhnlich mit der Endung _r. Aber dies ist kein Standard und somit für Netzwerkanwendungen, die ja gewöhnlich möglichst portabel sein sollten, eher ungeeignet.


Als Programmbeispiel soll auch hierzu wieder der Server verwendet werden, mit dem sich Dateien über das Netzwerk kopieren lassen. Allzu viel müssen Sie dabei im Gegensatz zum Beispiel des parallelen Servers mit fork() gar nicht ändern, um die Serveranwendung multithreading-fähig zu machen. Die Clientanwendung hingegen muss nicht verändert werden. Hierfür können Sie wieder dieselbe wie gehabt verwenden. Hier nun der Server, der Threads anstatt Prozesse verwendet, um einzelne Dateien über ein Netzwerk zu kopieren.


Hinweis   Das Programm verwendet Linux-Threads und ist somit nicht unbedingt auf anderen UNIXen lauffähig. Voraussetzung sind Linux-Threads. Speziell unter FreeBSD müssen Sie die linuxthreads aus den ports installieren und das Programm folgendermaßen übersetzen:

$ gcc -o thserver thserver.c \ -I/usr/local/include/pthread/linuxthreads \ -L/usr/local/lib -llthread -llgcc_r

/* thserver.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd. h.>
#include <errno.h>
#include <pthread. h.>
#define  MAX_ZEICHEN  1024
#define  PORT_NUMMER  1234
static void * threading_socket( void *);
void copy (int);
pthread_t th;
static void *threading_socket (void *arg) {
  pthread_detach (pthread_self ());
  copy ((int) arg);
  close ((int) arg);
  return NULL;
}
static void copy (int connfd) {
  int  fd;
  ssize_t j, n, ngesamt;
  char puffer[MAX_ZEICHEN];
  char path_file[MAX_ZEICHEN];
  printf (" ... Daten empfangen\n");
  /* Dateiname */
  j = 0;
  while ((n = read (connfd, &puffer[j], 1)) > 0) {
    if (puffer[j] == '\n') {
        puffer[j] = 0;
        break;
    }
    j++;
  }
  if (n < 0) {
      printf ("Fehler bei read() ...\n");
      exit (EXIT_FAILURE);
  }
  printf ("Dateiname \"%s\"  wird kopiert nach ", puffer);
  strcpy (path_file, getenv ("HOME"));
  strcat (path_file, "/tmp/");
  strcat (path_file, puffer);
  printf ("%s\n", path_file);
  /* Datei zum Lesen öffnen */
  if ((fd = open ( path_file, 
                 O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) {
    printf ("... kann %s nicht öffnen (%s)\n",
       puffer, strerror(errno));
    close (connfd);
  }
  /* Datei aus dem Socket lesen und in lokale Kopie schreiben */
  ngesamt = 0;
  while ((n = read (connfd, puffer, sizeof (puffer))) > 0) {
    if (write (fd, puffer, n) != n) {
        printf ("Fehler bei write() ...(%s)\n",
           strerror(errno));
        exit (EXIT_FAILURE);
    }
    ingesamt += n;
  }
  if (n < 0) {
      printf ("Fehler bei read() ...\n");
  }
  printf ("... beendet (%d Bytes)\n", ingesamt);
  close (fd);
  close (connfd);
  return;
}
int main (void) {
  int sockfd, connfd;
  struct sockaddr_in adresse;
  size_t adrlaenge = sizeof (struct sockaddr_in);
  const int y = 1;
  if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) < 0) {
     printf ("Fehler bei socket() ...(%s)\n",
        strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("Socket erfolgreich angelegt\n");
  adresse.sin_family = AF_INET;
  adresse.sin_port = htons (PORT_NUMMER);
  memset (&adresse.sin_addr, 0, sizeof (adresse.sin_addr));
  setsockopt( sockfd, SOL_SOCKET, 
              SO_REUSEADDR, &y, sizeof(int));
  if (bind ( sockfd,
             (struct sockaddr *) &adresse,
             sizeof (adresse) ) ) {
     printf ("Fehler bei bind() ...(%s)\n",
        strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("Server ist bereit und wartet ...\n");
  if (listen (sockfd, 5)) {
     printf ("Fehler bei listen() ...(%s)\n",
        strerror(errno));
     exit (EXIT_FAILURE);
  }
  while (1) {
    connfd = accept ( sockfd,
                      (struct sockaddr *) &adresse,
                      &adrlaenge );
    if (connfd < 0) {
      if (errno == EINTR)
         continue;
      else {
         printf ("Fehler bei accept() ...\n");
         exit (EXIT_FAILURE);
      }
    }
    pthread_create(&th, NULL, &threading_socket, connfd);
  }
  exit (EXIT_SUCCESS);
}

Ein Abdrucken des Programms bei der Ausführung kann ich mir hierbei ersparen, da sich nicht allzu viel im Gegensatz zu den Beispielen zuvor geändert hat. Außer in der Schleife des Servers hat sich auch nicht allzu viel im Listing verändert, nur dass das Beispiel jetzt ein wenig modularer aufgebaut ist.

  while (1) {
    connfd = accept ( sockfd,
                      (struct sockaddr *) &adresse,
                      &adrlaenge );
    if (connfd < 0) {
      if (errno == EINTR)
         continue;
      else {
         printf ("Fehler bei accept() ...\n");
         exit (EXIT_FAILURE);
      }
    }
    pthread_create(&th, NULL, &threading_socket, connfd);
  }

Hier wird ähnlich vorgegangen wie bei der Prozesserzeugung mittels fork() – mit dem Unterschied, wenn accept() zurückkehrt, dass hier ein Thread mit pthread_create() erzeugt wird anstatt ein neuer Prozess mit fork(). Jede Clientanfrage startet von nun an einen neuen Thread, und zwar den Thread threading_socket(). Zusätzlich übergeben Sie dem Thread als Argument den Deskriptor für das verbundene Socket (connfd).

static void * threading_socket (void *arg) {
   pthread_detach (pthread_self ());
   copy ((int) arg);
   close ((int) arg);
   return NULL;
}

In der Thread-Funktion threading_socket() wird der Thread mittels pthread_detach() ausgehängt, das heißt, der Haupt-Thread wartet nicht mehr auf die Rückkehr des von ihm angelegten Threads. Im nächsten Schritt wird dann die Funktion copy() aufgerufen, mit welcher der Kopiervorgang zwischen dem Client und dem Server-Thread abgearbeitet wird. Der Vorgang entspricht auch hier wieder demselben Code, den Sie schon im Beispiel des parallelen Servers mit fork() zuvor im Kindprozess kennen gelernt haben. Der Autor hat hierbei lediglich ein Copy & Paste gemacht. Als Argument übergeben Sie auch hier der Funktion den Deskriptor für das verbundene Socket. Wichtig ist dann, dass Sie vor der Rückkehr der Funktion threading_socket() das Socket mit close() schießen, da Threads ja alle Socket-Deskriptoren gemeinsam mit dem Main-Thread verwenden. Bei fork() ging dieser Vorgang ja nach Beendigung des Kindprozesses automatisch vor sich, da, wenn sich ein Prozess beendet, alle offenen Deskriptoren automatisch geschlossen werden.

 << 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