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.7 Aufbau eines Clientprogramms  downtop

Jetzt haben Sie zwar einige grundlegende Funktionen kennen gelernt – aber dies hilft Ihnen relativ wenig, wenn Sie nicht wissen, wie Sie diese in der Praxis einsetzen können. Da die meisten Netzwerkprogramme auf das so genannte Server-/Clientprinzip aufbauen, soll Ihnen hier ein einfaches Beispiel demonstriert werden.

Zuerst soll mit der Clientanwendung begonnen werden. Als Client wird gewöhnlich das Programm bezeichnet, das die Verbindung initiiert hat. Als Server hingegen wird die Anwendung bezeichnet, die den Verbindungsaufbauwunsch entgegennimmt.

Wichtig! Sie benötigen die Adresse und die Portnummer des Servers. Dabei stehen Ihnen u. a. folgende Möglichkeiten zur Verfügung:

gp  Angabe über Kommandozeilenargumente (./client 123.12.32.1:8080)
gp  Abfrage in der Anwendung (Konsole oder GUI, falls vorhanden)
gp  Feste Angabe im Quelltext
gp  Nutzung einer Datenbank

Welche Art und Möglichkeit Sie dabei verwenden, hängt immer vom jeweiligen Anwendungsfall ab und wie flexibel Ihr Programm sein muss. Darüber sollten Sie sich immer zuerst Gedanken machen.

Jetzt zur Programmierung einer Clientanwendung. Zuerst müssen Sie einen Socket erstellen. Egal ob Client- oder Serveranwendung – wenn eine Kommunikation zustande kommen soll, benötigen Sie immer ein Socket für jeden Prozess (unsere Steckdose eben).

int create_socket;
...
if ((create_socket = socket (AF_INET, SOCK_STREAM, 0)) > 0)
    printf ("Socket wurde angelegt\n");

Im nächsten Schritt, wenn die Clientanwendung erfolgreich ein Socket anlegen konnte, wird versucht, eine Verbindung zu einem anderen Rechner mit connect() aufzubauen. Für den Verbindungsaufbau wird eine Endpunktadresse benötigt. Diese wird als zweiter Parameter der Funktion connect() mit der Struktur sockaddr angegeben:

struct sockaddr {
   sa_family_t  sa_family; // Adressfamilie, AF_xxx
   char sa_data[14];       // 14 Bytes für Protokolladresse
};

Diese Struktur lässt sich allerdings recht unbequem ausfüllen (gemeint sind damit Portnummer und Internetadresse in der Strukturvariablen sa_data), weshalb es für IP-Anwendungen eine spezielle Struktur dafür gibt:

struct sockaddr_in {
    sa_family_t sin_family;        // Addressfamilie
    unsigned short int sin_port;   // Portnummer
    struct in_addr sin_addr;       // Internet-Adresse
};

sockaddr_in ermöglicht es Ihnen, die IP-Adresse sowie die Portnummer getrennt einzutragen. Im Speicher sind diese beiden Strukturen kompatibel, es reicht also eine einfache Typumwandlung, um connect() die gewünschten Informationen zu übergeben. Näheres zu den Strukturen sockaddr und sockaddr_in entnehmen Sie bitte Abschnitt 11.6.2.

Beim Ausfüllen der Struktur sockaddr muss allerdings auf die verschiedenen Architekturen Rücksicht genommen werden, denn auf den verschiedenen Architekturen gibt es unterschiedliche Anordnungen der Bytes zum Speichern von Zahlen. So wird die Anordnung gewöhnlich zwischen Big Endian und Little Endian unterschieden. Man spricht dabei gerne vom Zahlendreher. Beim Big Endian wird das höchstwertige Byte an der niedrigsten Adresse gespeichert, das zweithöchste an der nächsten Adresse und so weiter. Bei der Anordnung von Little Endian ist dies genau umgekehrt. Dabei wird das niedrigstwertige Byte an der niedrigsten Stelle gespeichert, das zweitniedrigste an der nächsten Stelle usw.

Da man sich mit Big Endian (auch als Network Byte Order bezeichnet) auf eine einheitliche Datenübertragung geeinigt hat, brauchen Sie sich keine Gedanken um verschiedene Architekturen zu machen.

Um jetzt aus einer lokal verwendeten Byte-Reihenfolge (Host Byte Order) eine Network-Byte-Order-Reihenfolge und umgekehrt zu konvertieren, stehen Ihnen die folgenden Funktionen zur Verfügung:

#include <netinet/in.h>
// Rückgabe : network-byte-order
// Parameter: host-byte-order       
unsigned short int htons(unsigned short int hostshort);
// Rückgabe : network-byte-order
// Parameter: host-byte-order   
unsigned long int htonl(unsigned long int hostlong);
// Rückgabe  :  host-byte-order
// Parameter : network-byte-order   
unsigned short int ntohs(unsigned short int netshort);
// Rückgabe :  host-byte-order
// Parameter : network-byte-order   
unsigned long int ntohl(unsigned long int netlong);

Dies mag jetzt für denjenigen, der sich noch nicht mit architekturspezifischen Problemen befassen musste, ein wenig verwirrend sein, doch sollte man sich davon nicht abschrecken lassen.

Weiter geht es mit der Clientanwendung. Nach dem Anlegen des Sockets wird es Zeit, eine Verbindung mit dem Server herzustellen:

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
...
struct sockaddr_in address;
...
address.sin_family = AF_INET;
address.sin_port = htons (15000);
inet_aton (argv[1], &address.sin_addr);
if (connect ( create_socket,
              (struct sockaddr *) &address,
              sizeof (address) ) == 0)
   /* connect() war erfolgreich */

Hinweis   Die Funktion inet_aton() konvertiert die IP-Adresse, die Sie als zweites Argument in der Kommandozeile (und somit ist es ein String) angeben, von der punktierten Darstellung in einen nummerischen Wert. Mehr dazu später.


Wenn bisher alles glatt verlaufen ist, sind Sie jetzt mit dem Server verbunden und können von nun an miteinander kommunizieren – sprich, Sie können mit send() oder write() Daten an den Server senden oder mit recv() oder read() Daten empfangen – je nach Anwendung.

Am Ende der Datenübertragung müssen Sie die Verbindung mittels close() wieder schließen. Das ist alles, mehr ist nicht für die Clientanwendung nötig.


Rheinwerk Computing

11.7.1 Zusammenfassung: Clientanwendung und Quellcode  toptop

Nochmals eine kurze Zusammenfassung, was zum Clientteil gehört:

gp  einen Socket erzeugen – socket()
gp  Verbindung zum Server herstellen – connect()
gp  Kommunikation mit dem Server – hängt vom Anwendungsfall ab
gp  Verbindung wieder beenden – close()

Hierzu der Quellcode der Clientanwendung:

/* client.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 (int argc, char **argv) {
  int create_socket;
  char *buffer = malloc (BUF);
  struct sockaddr_in address;
  int size;
  if( argc < 2 ){
     printf("Usage: %s ServerAdresse\n", *argv);
     exit(EXIT_FAILURE);
  }
  printf ("\e[2J");
  if ((create_socket=socket (AF_INET, SOCK_STREAM, 0)) > 0)
    printf ("Socket wurde angelegt\n");
  address.sin_family = AF_INET;
  address.sin_port = htons (15000);
  inet_aton (argv[1], &address.sin_addr);
  if (connect ( create_socket,
                (struct sockaddr *) &address,
                sizeof (address)) == 0)
    printf ("Verbindung mit dem Server (%s) hergestellt\n",
       inet_ntoa (address.sin_addr));
  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;
}

Hinweis   Beachten Sie, dass für *BSD beim Kompilieren immer die Headerdatei sys/types.h noch vor sys/socket.h inkludiert werden muss, sonst gibt es einen Fehler beim Kompilieren. Dies nur für den Fall, dass ich es vergessen sollte zu erwähnen und Sie unter *BSD eine seltsame Fehlermeldung erhalten.


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