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.19 Multicast-Sockedowntop

Wenn es die Umstände erfordern, dass ein Server Datagramme gleichzeitig an mehrere Clients verschicken muss, können Sie Folgendes tun: Entweder Sie bauen mehrere einzelne Verbindungen (man spricht dabei von Unicast-Verbindungen) auf, was allerdings sehr ineffektiv ist und die Bandbreite des Servers erheblich belastet, oder Sie verwenden Multicast-Verbindungen. Hinter Multicast verbirgt sich eine Technik, um Ihnen die Arbeit einfacher in Bezug einer UDP-Verbindung zu machen.

Multicast ist eine beliebte Technik, die u. a. in Anwendungen wie Videokonferenzen oder interaktiven Online-Spielen eingesetzt wird. Multicast-Sockets lassen sich zum Glück auch noch relativ einfach entwickeln. Dabei handelt es sich nicht einmal um ein neues API, sondern es werden lediglich fünf neue Socket-Optionen benötigt, die mit der Funktion setsockopt() gesetzt werden. Zur Übertragung der Daten wird das UDP-Protokoll verwendet. Um Multicast-Anwendungen zu schreiben, benötigen Sie eine spezielle IP-Adresse. Dazu wird der Adressraum der Klasse D im Bereich von 224.0.0.0 bis 239.255.255.255 verwendet. Die unteren 28 Bit werden dabei als Multicast-Gruppen-ID und die 32-Bit-Adresse als Gruppenadresse bezeichnet.

Prüfen Sie erst, ob Multicast überhaupt in den aktuellen Kernel einkompiliert ist:

# gzip -cd /proc/config.gz | egrep \
  "IP_MULTICAST|IP_MROUTE|IP_PIMSM"
CONFIG_IP_MULTICAST=y
CONFIG_IP_MROUTE=y
CONFIG_IP_PIMSM_V1=y
CONFIG_IP_PIMSM_V2=y

Da nicht alle Linux-Systeme für IP Multicast-Adressen konfiguriert sind, kann es sein, dass hier ein wenig Nacharbeit fällig wird. Gewöhnlich ist die Multicast-IP-Funktion nur abgeschaltet und kann recht einfach eingerichtet werden. Machen Sie sich zum Superuser, und geben Sie Folgendes in der Konsole ein:

# route add -net 224.0.0.0 netmask 240.0.0.0 dev lo
# route -e
Kernel IP Routentabelle
Ziel       Router  Genmask     Flags  MSS Fenster irtt Iface
224.0.0.0  *       240.0.0.0   U        0 0          0 lo
#

Wenn zum Beispiel Ihre lokale Adresse zuvor 127.x.x.x war, lautet diese jetzt 224.x.x.x. Sollte sich partout keine Multicast-IP einrichten lassen, kann es sein, dass der aktuelle Kernel dies im Augenblick nicht unterstützt. Dann müssen Sie Ihren Kernel neu »bauen«. Wie dies genau funktioniert, entnehmen Sie bitte der jeweiligen Linux-Distribution. Gewöhnlich sollte der Vorgang überall ähnlich ablaufen. Um also den Kernel mit Multicast-Unterstützung neu zu erstellen, müssen Sie bei make menuconfig nach der Option select networking options Ausschau halten und ein Häkchen vor »IP: multicasting« und am besten auch gleich vor »IP: multicasting routing« sowie »PIM-SM version 1« und version 2 setzen.

Damit die Netzwerk-API Multicasting unterstützt, sind lediglich fünf neue Optionen nötig. Hierzu eine kurze Beschreibung der Socket-Optionen, die mit den Funktionen getsockopt() und setsockopt() erfragt bzw. gesetzt werden können. Beide Funktionen wurden ja zuvor schon näher beschrieben.

gp  IP_ADD_MEMBERSHIP – Damit schließen Sie das Socket an eine bestimmte Multicast-Gruppe an. Als Argument des Datentyps für setsockopt() oder getsockopt() wird die Struktur ip_mreq erwartet:
struct ip_mreq   { //Ipc4-KlasseD-Multicast-Adresse struct in_addr imr_multiaddr; //Ipv4-Adresse der lokalen Schnittstelle struct in_addr imr_interface;  }
gp  IP_DROP_MEMBERSHIP – Das Gegenstück zu IP_ADD_MEMBERSHIP. Damit verlässt das Socket die Multicast-Gruppe. Als Argument des Datentyps für setsockopt() oder getsockopt() wird auch hier die Struktur ip_mreq erwartet.
gp  IP_MULTICAST_IF – Damit wird die Schnittstelle für ausgehende Multicast-Pakete festgelegt, worauf Sockets verschickt werden. Als Argument des Datentyps für setsockopt() oder getsockopt() wird die Struktur ip_mreq erwartet.
gp  IP_MULTICAST_TTL – Hier wird TTL für das ausgehende Multicast festgelegt. Als Argument des Datentyps für setsockopt() oder getsockopt() wird u_char erwartet.
gp  IP_MULTICAST_LOOP – Damit lässt sich der lokale Loopback von Multicast-Datagrammen aktivieren bzw. deaktivieren. Als Argument des Datentyps für setsockopt() oder getsockopt() wird u_char erwartet.

Hinweis   Bei IPv6 wird vor die Socket-Optionen IPV6_ anstatt IP_ geschrieben. Z. B. aus IP_ADD_MEMBERSHIP wird IPV6_ADD_MEMBERSHIP. Die Struktur ip_mreq heißt hierbei dann auch ipv6_mreq.


Hierzu soll ein einfaches Beispiel realisiert werden. Im Beispiel wird wieder davon ausgegangen, dass Sie die Anwendung auf dem lokalen Rechner ausführen, was bedeutet, wie oben eingerichtet, dass hierbei die IP-Adresse 224.0.0.1 verwendet wird. Zuerst folgt die Serveranwendung.

/* server.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd. h.>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static int port = 1234;
int main (void) {
  int socket_descriptor;
  struct sockaddr_in address;
  socket_descriptor = socket (AF_INET, SOCK_DGRAM, 0);
  if (socket_descriptor == -1) {
     perror ("socket()");
     exit (EXIT_FAILURE);
  }
  memset (&address, 0, sizeof (address));
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = inet_addr ("224.0.0.1");
  address.sin_port = htons (port);
  printf ("Server ist bereit ...\n");
  /* Broadcasting beginnen */
  while (1) {
    if (sendto( socket_descriptor,
                "broadcast test (hallo client)",
                sizeof ("broadcast test (hallo client)"),
                0,
                (struct sockaddr *) &address,
                sizeof (address)) < 0) {
       perror ("sendto()");
       exit (EXIT_FAILURE);
    }
    sleep (1);
  }
  return EXIT_SUCCESS;
}

Der einzige Unterschied zu herkömmlichen Unicast-Verbindungen liegt hier in der IP-Adresse, womit der Server mit dem Client kommuniziert:

address.sin_addr.s_addr = inet_addr ("224.0.0.1");

Die Clientanwendung gestaltet sich ein wenig umfangreicher. Daher wurde der Code, mit dem eine Multicast-Verbindung eingerichtet und aufgebaut wird, extra in eine Funktion namens setup_multicast_socket() zusammengefasst. Hierzu der komplette Quellcode zum Client mit anschließender Erläuterung.

/* client.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd. h.>
#include <string.h>
#include <errno.h>
/* Adresse für multicast IP */
static char *host_name = "224.0.0.1";
static int port = 1234;
static struct ip_mreq command;
static int setup_multicast_socket (void) {
  int loop = 1;
  int socket_descriptor;
  struct sockaddr_in sin;
  memset (&sin, 0, sizeof (sin));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl (INADDR_ANY);
  sin.sin_port = htons (port);
  if ( ( socket_descriptor = socket(PF_INET,
                                    SOCK_DGRAM, 0)) == -1) {
     perror ("socket()");
     exit (EXIT_FAILURE);
  }
  /* Mehr Prozessen erlauben, denselben Port zu nutzen */
  loop = 1;
  if (setsockopt ( socket_descriptor,
                   SOL_SOCKET,
                   SO_REUSEADDR,
                   &loop, sizeof (loop)) < 0) {
     perror ("setsockopt:SO_REUSEADDR");
     exit (EXIT_FAILURE);
  }
  if(bind( socket_descriptor,
           (struct sockaddr *)&sin,
           sizeof(sin)) < 0) {
     perror ("bind");
     exit (EXIT_FAILURE);
  }
  /* Broadcast auf dieser Maschine zulassen */
  loop = 1;
  if (setsockopt ( socket_descriptor,
                   IPPROTO_IP,
                   IP_MULTICAST_LOOP,
                   &loop, sizeof (loop)) < 0) {
     perror ("setsockopt:IP_MULTICAST_LOOP");
     exit (EXIT_FAILURE);
  }
  /* Join the broadcast group: */
  command.imr_multiaddr.s_addr = inet_addr ("224.0.0.1");
  command.imr_interface.s_addr = htonl (INADDR_ANY);
  if (command.imr_multiaddr.s_addr == -1) {
     perror ("224.0.0.1 ist keine Multicast-Adresse\n");
     exit (EXIT_FAILURE);
  }
  if (setsockopt ( socket_descriptor,
                   IPPROTO_IP,
                   IP_ADD_MEMBERSHIP,
                   &command, sizeof (command)) < 0) {
    perror ("setsockopt:IP_ADD_MEMBERSHIP");
  }
  return socket_descriptor;
}
int main (void) {
  int iter = 0;
  int sin_len;
  char message[256];
  int socket;
  struct sockaddr_in sin;
  struct hostent *server_host_name;
  if ((server_host_name = gethostbyname (host_name)) == 0) {
     perror ("gethostbyname");
     exit (EXIT_FAILURE);
  }
  socket = setup_multicast_socket ();
  /* Broadcast-Nachrichten empfangen */
  while (iter++ < 10) {
     sin_len = sizeof (sin);
     if (recvfrom( socket, message, 256, 0,
                  (struct sockaddr *) &sin, &sin_len)==-1) {
        perror ("recvfrom");
    }
    printf ("Antwort #%-2d vom Server: %s\n",
       iter, message);
    sleep (2);
  }
  /* Multicast-Socket aus der Gruppe entfernen */
  if (setsockopt ( socket,
                   IPPROTO_IP,
                   IP_DROP_MEMBERSHIP,
                   &command, sizeof (command)) < 0 ) {
      perror ("setsockopt:IP_DROP_MEMBERSHIP");
  }
  close (socket);
  return EXIT_SUCCESS;
}

Zuerst wird im Listing über den gewöhnlichen Weg ein Socket eingerichtet (gethostbyname() und socket()). Im nächsten Schritt richten Sie das Socket mit setsockopt() ein, womit dieses erlaubt, 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 (weshalb auch in allen obigen Codesnippets setsockopt hinzugefügt wurde).

   loop = 1;
   if (setsockopt ( socket_descriptor,
                    SOL_SOCKET, 
                    SO_REUSEADDR,
                    &loop, sizeof (loop)) < 0 ) {
       perror ("setsockopt:SO_REUSEADDR");
       exit (EXIT_FAILURE);
   }

Das Setzen dieser Option hat allerdings noch überhaupt nichts mit dem Multicasting zu tun. Betrachten Sie es jetzt einfach als Beiwerk des Listings. Anschließend wird das Socket über den gewöhnlichen Weg gebunden (bind()).

Nach dem Binden richten Sie das Socket für das Broadcasting auf der Maschine ein – genauer den lokalen Loopback der Multicast-Datagramme:

   loop = 1;
   if (setsockopt ( socket_descriptor,
                    IPPROTO_IP,
                    IP_MULTICAST_LOOP,
                    &loop, sizeof (loop)) < 0)  {
       perror ("setsockopt:IP_MULTICAST_LOOP");
       exit (EXIT_FAILURE);
   }

Jetzt, nachdem Sie die Erlaubnis haben, über die Maschine eine Multicast-Verbindung einzugehen, müssen Sie den Linux-Kernel über die Daten des speziellen Sockets informieren und sich der Multicast-Gruppe anschließen:

   command.imr_multiaddr.s_addr = inet_addr ("224.0.0.1");
   command.imr_interface.s_addr = htonl (INADDR_ANY);
   if (command.imr_multiaddr.s_addr == -1) {
       perror ("224.0.0.1 ist keine Multicast-Adresse\n");
       exit (EXIT_FAILURE);
   }
   if (setsockopt ( socket_descriptor,
                    IPPROTO_IP, 
                    IP_ADD_MEMBERSHIP,
                    &command, sizeof (command) ) < 0 )  {
       perror ("setsockopt:IP_ADD_MEMBERSHIP");
   }

Jetzt ist das Socket für die Multicast-Verbindung eingerichtet. Anschließend können Sie, in die main()-Funktion zurückgekehrt, am Port mit recvfrom() lauschen.

In diesem Beispiel wurde eine Schleife eingerichtet, die nach zehn Durchgängen mit der Arbeit fertig ist. Am Ende der Multicast-Sitzung müssen Sie die Multicast-Gruppe wieder ordentlich verlassen und das Socket schließen:

   if (setsockopt ( socket,
                    IPPROTO_IP, 
                    IP_DROP_MEMBERSHIP,
                    &command, sizeof (command) ) < 0 ) {
       perror ("setsockopt:IP_DROP_MEMBERSHIP");
   }

Das Programm bei der Ausführung:

$ gcc -o server server.c
$ gcc -o client client.c
$ ./server
Server ist bereit ...
---[neue Konsole]---
$ ./client
Antwort #1  vom Server: broadcast test (hallo client)
Antwort #2  vom Server: broadcast test (hallo client)
Antwort #3  vom Server: broadcast test (hallo client)
Antwort #4  vom Server: broadcast test (hallo client)
Antwort #5  vom Server: broadcast test (hallo client)
Antwort #6  vom Server: broadcast test (hallo client)
Antwort #7  vom Server: broadcast test (hallo client)
Antwort #8  vom Server: broadcast test (hallo client)
Antwort #9  vom Server: broadcast test (hallo client)
Antwort #10 vom Server: broadcast test (hallo client)

Sie können während der Zeit, nachdem Sie den ersten Client gestartet haben, gerne weitere Konsolen öffnen und weitere Clients starten. Kein Client muss dabei warten und wird genauso abgearbeitet wie der erste Client. Bei einer gewöhnlichen Unicast-Verbindung wären der Aufwand des Programms und auch der Aufwand der Bandbreite erheblich umfangreicher gewesen.


Rheinwerk Computing

11.19.1 Anwendungsgebiete von Multicast-Verbindungen  toptop

Ganz klar, die wichtigste Frage dürfte lauten, wann und wo setzt man am besten Multicast-Verbindungen ein. Natürlich ist dies nur sinnvoll für Anwendungen, die eine 1:n-Kommunikation erfordern. Und dies könnten u. a. folgende Anwendungsgebiete sein:

gp  Interaktive Simulationen, Online-Spiele
gp  Effiziente Verteilung von Daten an eine große Anzahl von Empfängern (Software-Updates, Newsticker ...)
gp  Fileserver
gp  Clustering
gp  Multimedia-Anwendungen
Videokonferenz Near-Video-on-Demand Verteilkommunikation (Broadcast)
 << 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