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.18 UNIX-Domain-Sockets (IPCdowntop

Die UNIX-Domain-Sockets sind eigentlich keine wirklichen »Sockets« im Sinne der Netzwerkprogrammierung und gehören eigentlich ins Kapitel der Interprozesskommunikation (IPC), da diese Sockets nur für den lokalen Rechner verwendet werden können. Aber um eben die UNIX-Domain-Sockets verwenden und vor allem verstehen zu können, benötigt man eben auch das Wissen zu den Sockets allgemein – daher können Sie dieses Kapitel als nachgereichtes IPC-Kapitel verstehen.

Die Verwendung von UNIX-Domain-Sockets als IPC ist recht beliebt, da diese recht einfach und flexibel angewandt werden können. Statt IP-Adressen werden zu Kommunikation zwischen den Socket-Endpunkten Dateien verwendet, die im Dateisystem angelegt werden, wenn ein Socket an eine Datei gebunden wird. Gewöhnlich werden die UNIX-Domain-Sockets als Stream-Schnittstelle verwendet, obgleich auch hier eine Datagramm-Schnittstelle möglich ist.

Die UNIX-Domain-Sockets arbeiten verbindungsorientiert, was bedeutet, dass Sie immer erst eine Verbindung zwischen zwei Prozessen aufbauen müssen, die miteinander kommunizieren. Die Datenübertragung beider Prozesse kann im Normalfall von keinem anderen Prozess mitgelesen werden. Da hier immer eine private Verbindung zwischen den Prozessen besteht, müssen Sie bei einem Server, wenn dieser mit mehreren Prozessen gleichzeitig kommunizieren soll, für jeden Kommunikationskanal einen eigenen Filedeskriptor einrichten.

Besondere Eigenschaften von UNIX-Domain-Sockets sind, dass diese vollduplex und auch schneller als TCP-Sockets sind. Letzteres ist natürlich nur von Vorteil, wenn sich der Server und der Client auf demselben Rechner befinden. Das X-Window-System nutzt daher die UNIX-Domain-Sockets.


Rheinwerk Computing

11.18.1 Die Adressstruktur von UNIX-Domain-Sockets  downtop

Die Struktur von UNIX-Domain-Sockets lautet sockaddr_un und ist in <sys/un.h> wie folgt definiert:

struct sockaddr_un {
   uint8_t     sun_len;    // Länge Struktur - Nicht POSIX   
   sa_family_t sun_family; // PF_UNIX, AF_UNIX, PF_LOCAL...
   char     sun_path[104]; // Pfadname
};

Mit sun_family müssen Sie bei UNIX-Domain-Socket die Konstante PF_UNIX, AF_UNIX bzw. PF_LOCAL, AF_LOCAL setzen. sun_path hingegen muss mit einem terminierten Dateinamen (mit Pfadangabe) beschrieben werden, der für die Verbindung verwendet werden soll. Diese Datei wird vom Serverprozess beim Aufrufen der Funktion bind() angelegt. Wichtig: Existiert diese Datei bereits, so schlägt der Funktionsaufruf von bind() fehl. Daher sollte man ggf. die Datei zuvor mit unlink() immer entfernen (egal ob diese nun existiert oder nicht).

Abgesehen von dieser Struktur funktionieren die UNIX-Domain-Sockets ansonsten ähnlich wie die TCP-Sockets. Daher soll wieder ein einfaches Client-Server-Beispiel erstellt werden, das genauso funktioniert wie schon das erste Client-Server-Beispiel (mit TCP-Sockets) in diesem Kapitel, nur dass eben dieses Mal UNIX-Domain-Sockets dazu verwendet werden. Zunächst der Quellcode zum Server:

/* uds_server.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd. h.>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUF 1024
#define UDS_FILE "/tmp/sock.uds"
int main (void) {
  int create_socket, new_socket;
  socklen_t addrlen;
  char *buffer = malloc (BUF);
  ssize_t size;
  struct sockaddr_un address;
  const int y = 1;
  printf ("\e[2J");
  if((create_socket=socket (AF_LOCAL, SOCK_STREAM, 0)) > 0)
    printf ("Socket wurde angelegt\n");
  unlink(UDS_FILE);
  address.sun_family = AF_LOCAL;
  strcpy(address.sun_path, UDS_FILE);
  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 ist verbunden ...\n");
     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 noch das Beispiel zum Client:

/* uds_client.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd. h.>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUF 1024
#define UDS_FILE "/tmp/sock.uds"
int main (int argc, char **argv) {
  int create_socket;
  char *buffer = malloc (BUF);
  struct sockaddr_un address;
  int size;
  printf ("\e[2J");
  if((create_socket=socket (PF_LOCAL, SOCK_STREAM, 0)) > 0)
    printf ("Socket wurde angelegt\n");
  address.sun_family = AF_LOCAL;
  strcpy(address.sun_path, UDS_FILE);
  if (connect ( create_socket,
                (struct sockaddr *) &address,
                sizeof (address)) == 0)
    printf ("Verbindung mit dem Server hergestellt\n");
  do {
      size = recv(create_socket, buffer, BUF-1, 0);
      if( size > 0)
         buffer[size] = '\0';
      printf ("Nachricht erhalten: %s\n", buffer);
      if (strcmp (buffer, "quit\n")) {
         printf ("Nachricht zum Versenden: ");
         fgets (buffer, BUF, stdin);
         send(create_socket, buffer, strlen (buffer), 0);
       }
  } while (strcmp (buffer, "quit\n") != 0);
  close (create_socket);
  return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

[ tty1 ]
$ gcc -o uds_server uds_server.c
$ gcc -o uds_client uds_client.c
$ ./uds_server
Socket wurde angelegt
[ tty2 ]
$ ./uds_client
Socket wurde angelegt
Verbindung mit dem Server hergestellt
[ tty1 ]
$ ./uds_server
Socket wurde angelegt
Ein Client ist verbunden ...
Nachricht zum Versenden: Hallo Client
[ tty2 ]
$ ./uds_client
Socket wurde angelegt
Verbindung mit dem Server hergestellt
Nachricht erhalten: Hallo Client
Nachricht zum Versenden: Hallo Server
[ tty1 ]
$ ./uds_server
Socket wurde angelegt
Ein Client ist verbunden ...
Nachricht zum Versenden: Hallo Client
Nachricht empfangen: Hallo Server
Nachricht zum Versenden: ...

Rheinwerk Computing

11.18.2 Lokale Sockets erzeugen – socketpair()  toptop

Mit socketpair() haben Sie eine Funktion, womit Sie lokale Sockets erzeugen können:

#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain,int typ,int protocol,int sv[2]);

Damit generieren Sie zwei unbenannte, miteinander verbundene Sockets. Für den Parameter domain muss man bei lokalen Sockets gewöhnlich AF_LOCAL bzw. PF_LOCAL oder auch AF_UNIX bzw. PF_UNIX angeben. Für protocol gibt man hier 0 und für den typ eben SOCK_STREAM oder SOCK_DGRAM an. Die Socket-Deskriptoren werden in sv[0] und sv[1] zurückgegeben. Bei Erfolg gibt diese Funktion 0, ansonsten bei einem Fehler -1 zurück. Auf den zweiten Blick dürfte Ihnen wohl eine Ähnlichkeit zur Funktion pipe() (gemischt mit socket()) aufgefallen sein. Und in der Tat kann diese Funktion ähnlich wie pipe() verwendet werden, nur mit dem entscheidenden Vorteil, dass die UNIX-Domain-Sockets vollduplex sind – Pipes hingegen sind ja nur halbduplex. Es kann also in beiden Socket-Deskriptoren gelesen und auch geschrieben werden. Hierzu ein einfaches Beispiel, das »vollduplex« und die Funktion socketpair() demonstrieren soll.

/* fullduplex.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>
#include <errno.h>
#define BUF 1024
int main(void) {
   int sv[2], c;
   char buf1[BUF], buf2[BUF];
   if(socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) == -1) {
      perror("Fehler bei socketpair");
      exit(EXIT_FAILURE);
   }
   printf("Eingabe machen: ");
   fgets(buf1, BUF, stdin);
   
   /* in sv[0] schreiben */
   write(sv[0], buf1, sizeof(buf1));
   /* aus sv[1] lesen */
   c = read(sv[1], buf2, sizeof(buf2));
   /* Terminieren */
   buf2[c]='\0';
   printf("sv[0] --> sv[1] : %s\n", buf2);
   /* Jetzt das Ganze in die andere Richtung */
   printf("Eingabe machen: ");
   fgets(buf1, BUF, stdin);
   
   /* in sv[1] schreiben */
   write(sv[1], buf1, sizeof(buf1));
   /* aus sv[1] lesen */
   c = read(sv[0], buf2, sizeof(buf2));
   /* Terminieren */
   buf2[c]='\0';
   printf("sv[1] --> sv[0] : %s\n", buf2);
   close(sv[0]);
   close(sv[1]);
   
   return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc -o fullduplex fullduplex.c
$ ./fullduplex
Eingabe machen: Teste socketpair zum Ersten
sv[0] --> sv[1] : Teste socketpair zum Ersten
Eingabe machen: Teste socketpair zum Zweiten
sv[1] --> sv[0] : Teste socketpair zum Zweiten
 << 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