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

Inhaltsverzeichnis
Vorwort
Vorwort des Gutachters
1 Einstieg in C
2 Das erste Programm
3 Grundlagen
4 Formatierte Ein-/Ausgabe mit »scanf()« und »printf()«
5 Basisdatentypen
6 Operatoren
7 Typumwandlung
8 Kontrollstrukturen
9 Funktionen
10 Präprozessor-Direktiven
11 Arrays
12 Zeiger (Pointer)
13 Kommandozeilenargumente
14 Dynamische Speicherverwaltung
15 Strukturen
16 Ein-/Ausgabe-Funktionen
17 Attribute von Dateien und das Arbeiten mit Verzeichnissen (nicht ANSI C)
18 Arbeiten mit variabel langen Argumentlisten – <stdarg.h>
19 Zeitroutinen
20 Weitere Headerdateien und ihre Funktionen (ANSI C)
21 Dynamische Datenstrukturen
22 Algorithmen
23 CGI mit C
24 MySQL und C
25 Netzwerkprogrammierung und Cross–Plattform-Entwicklung
26 Paralleles Rechnen
27 Sicheres Programmieren
28 Wie geht’s jetzt weiter?
A Operatoren
B Die C-Standard-Bibliothek
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
<< zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch
Buch: C von A bis Z

C von A bis Z
3., aktualisierte und erweiterte Auflage, geb., mit CD und Referenzkarte
1.190 S., 39,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1411-7
Pfeil 25 Netzwerkprogrammierung und Cross–Plattform-Entwicklung
Pfeil 25.1 Begriffe zur Netzwerktechnik
Pfeil 25.1.1 IP-Nummern
Pfeil 25.1.2 Portnummer
Pfeil 25.1.3 Host- und Domainname
Pfeil 25.1.4 Nameserver
Pfeil 25.1.5 Das IP-Protokoll
Pfeil 25.1.6 TCP und UDP
Pfeil 25.1.7 Was sind Sockets?
Pfeil 25.2 Headerdateien zur Socketprogrammierung
Pfeil 25.2.1 Linux/UNIX
Pfeil 25.2.2 Windows
Pfeil 25.3 Client/Server-Prinzip
Pfeil 25.3.1 Loopback-Interface
Pfeil 25.4 Erstellen einer Client-Anwendung
Pfeil 25.4.1 »socket()« – Erzeugen eines Kommunikationsendpunktes
Pfeil 25.4.2 »connect()« – ein Client stellt eine Verbindung zum Server her
Pfeil 25.4.3 Senden und Empfangen von Daten
Pfeil 25.4.4 »close()« und »closesocket()«
Pfeil 25.5 Erstellen einer Server-Anwendung
Pfeil 25.5.1 »bind()« – Festlegen einer Adresse aus dem Namensraum
Pfeil 25.5.2 »listen()« – Warteschlange für eingehende Verbindungen einrichten
Pfeil 25.5.3 »accept()« und die Serverhauptschleife
Pfeil 25.6 (Cross-Plattform-)TCP-Echo-Server
Pfeil 25.6.1 Der Client
Pfeil 25.6.2 Der Server
Pfeil 25.7 Cross-Plattform-Development
Pfeil 25.7.1 Abstraction Layer
Pfeil 25.7.2 Headerdatei für Linux/UNIX
Pfeil 25.7.3 Linux/UNIX-Quellcodedatei
Pfeil 25.7.4 Headerdatei für MS-Windows
Pfeil 25.7.5 Windows-Quellcodedatei
Pfeil 25.7.6 All together – die »main«-Funktionen
Pfeil 25.7.7 Ein UDP-Beispiel
Pfeil 25.7.8 Mehrere Clients gleichzeitig behandeln
Pfeil 25.8 Weitere Anmerkungen zur Netzwerkprogrammierung
Pfeil 25.8.1 Das Datenformat
Pfeil 25.8.2 Der Puffer
Pfeil 25.8.3 Portabilität
Pfeil 25.8.4 Von IPv4 nach IPv6
Pfeil 25.8.5 RFC-Dokumente (Request for Comments)
Pfeil 25.8.6 Sicherheit


Rheinwerk Computing - Zum Seitenanfang

25.4 Erstellen einer Client-Anwendung Zur nächsten ÜberschriftZur vorigen Überschrift

In diesem Abschnitt geht es darum, was für Funktionen grundlegend verwendet werden, um eine Client-Anwendung zu erstellen.


Rheinwerk Computing - Zum Seitenanfang

25.4.1 »socket()« – Erzeugen eines Kommunikationsendpunktes Zur nächsten ÜberschriftZur vorigen Überschrift

Der erste Schritt einer Kommunikationsverbindung – egal, ob dies auf dem Server oder auf dem Client geschieht – besteht immer erst einmal darin, einen Socket vom Betriebssystem anzufordern. Dabei ist noch egal, wer mit wem kommunizieren will. Das Erzeugen eines Sockets (Kommunikationsendpunkt) können Sie sich wie das Installieren einer Stromsteckdose vorstellen. Ähnlich wie bei den Stromsteckdosen weltweit, wo es ja auch unterschiedliche Formen und Spannungen gibt, muss auch beim Anlegen eines Sockets angegeben werden, was hier alles »eingesteckt« werden kann. Hierzu sehen Sie zuerst die Syntax der Funktion socket() für Linux/UNIX:

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

Und hier ist die Syntax für MS-Windows:

#include <winsock.h>

SOCKET socket(int af, int type, int protocol);

Auf beiden Systemen haben diese Funktionen eine fast identische Syntax – abgesehen vom Rückgabewert, der unter MS-Windows SOCKET lautet. Allerdings ist SOCKET letztendlich nichts anderes als eine Typdefinition von int, und somit könnten Sie in der Praxis hierfür auch int verwenden. Als Rückgabewert erhalten Sie bei beiden Versionen den Socket-Deskriptor.

Bei einem Fehler gibt die Linux/UNIX-Version –1 zurück. Den Fehler können Sie mit dem Fehlercode von errno auswerten (beispielsweise mit perror() oder strerror()).

Unter MS-Windows wird bei einem Fehler die Konstante SOCKET_ERROR (ebenfalls mit –1 definiert) zurückgegeben. Hierbei können Sie den Fehlercode mit der Funktion WSAGetLastError() ermitteln.

Mit dem ersten Parameter domain bzw. af geben Sie die Adressfamilie (d. h. die Protokollfamilie) an, die Sie verwenden wollen. Eine komplette Liste aller auf Ihrem System unterstützten Protokolle finden Sie in der Headerdatei <sys/socket.h>. Tabelle 25.1 enthält einen Überblick zu den gängigeren und häufiger verwendeten Protokollen.


Tabelle 25.1 Einige gängige Adressfamilien

Adressfamilie Bedeutung
AF_UNIX

UNIX Domain Sockets; wird gewöhnlich für lokale Interprozesskommunikation verwendet.

AF_INET

Internet IP-Protokoll Version 4 (IPv4)

AF_INET6

Internet IP-Protokoll Version 6 (IPv6)

AF_IRDA

IRDA-Sockets; beispielsweise via Infarot

AF_BLUETOOTH

Bluetooth-Sockets


Mit dem zweiten Parameter der Funktion socket() geben Sie den Socket-Typ an. Damit legen Sie die Übertragungsart der Daten fest. Für Sie sind hierbei erst einmal nur die symbolischen Konstanten SOCK_STREAM für TCP und SOCK_DGRAM für UDP interessant.

Mit dem dritten Parameter können Sie ein Protokoll angeben, das Sie zur Übertragung verwenden wollen. Wenn Sie hierfür 0 eintragen, was meistens der Fall ist, wird das Standardprotokoll verwendet, das dem gewählten Socket-Typ (zweiter Parameter) entspricht. Im Fall von SOCK_STREAM wird TCP und bei SOCK_DGRAM wird UDP verwendet. Weitere mögliche Werte, ohne jetzt genauer darauf einzugehen, wären hierbei IPPROTO_TCP (TCP-Protokoll), IPPROTO_UDP (UDP-Protokoll), IPPROTO_ICMP (ICMP-Protokoll) und IPPROTO_RAW (wird bei Raw-Sockets verwendet). Wenn Sie allerdings beispielsweise für den Socket-Typ SOCK_STREAM angegeben haben und das TCP-Protokoll verwenden wollen, müssen Sie nicht extra noch beim dritten Parameter IPPROTO_TCP angeben. Mit der Angabe von 0 wird dieses Protokoll standardmäßig verwendet.

Somit sieht das Anfordern eines Sockets folgendermaßen aus:

    // Erzeuge das Socket - Verbindung über TCP/IP
    sock = socket( AF_INET, SOCK_STREAM, 0 );
    if (sock < 0) {
        // Fehler beim Erzeugen des Sockets
    }

Hinweis

Damit hier keine Missverständnisse entstehen: Das Erzeugen eines Sockets muss auch auf der Serverseite durchgeführt werden. Womit sonst, wenn nicht über Sockets, will sich ein Client mit dem Server unterhalten?



Rheinwerk Computing - Zum Seitenanfang

25.4.2 »connect()« – ein Client stellt eine Verbindung zum Server her Zur nächsten ÜberschriftZur vorigen Überschrift

Nachdem mit den Sockets die Kommunikationsendpunkte erzeugt wurden, kann der Client nun versuchen, eine Verbindung zum Server-Socket herzustellen. Dies wird mit der Funktion connect() versucht, die unter Linux/UNIX folgende Syntax hat:

#include <sys/types.h>
#include <sys/socket.h>

int connect (
   int socket,
   const struct sockaddr *addr,
   int addrlen
);

Unter MS-Windows lautet die Syntax so:

#include <winsock.h>

int connect (
   SOCKET s,
   const struct sockaddr FAR* addr,
   int addrlen
);

Auch hier unterscheidet sich die Syntax nicht erheblich voneinander, und auch die Bedeutungen der einzelnen Parameter sind wieder dieselben. Bei einer erfolgreichen Ausführung geben beide Funktionen 0, ansonsten bei einem Fehler –1 (gleichwertig unter MS-Windows mit SOCKET_ERROR) zurück. Den Fehler können Sie auch hier wieder mit der Fehlervariablen errno (unter Linux/UNIX) oder mit der Funktion WSAGetLastError() (unter MS-Windows) ermitteln.

Als erster Parameter wird der Socket-Deskriptor erwartet, über den Sie die Verbindung herstellen wollen. Dies ist der Rückgabewert, den Sie von der Funktion socket() erhalten haben.

Um eine Verbindung zu einem anderen Rechner aufzubauen, werden logischerweise auch Informationen über die Adresse benötigt, mit der sich der Client verbinden will. Die Adressinformationen über den gewünschten Verbindungspartner tragen Sie im zweiten Parameter der Funktion connect() ein. Um sich mit dem Server zu verbinden, benötigen Sie Informationen über die Adressfamilie (Protokollfamilie), die Portnummer und logischerweise die IP-Adresse. Eingetragen werden diese Informationen mit dem zweiten Parameter der Struktur sockaddr, die folgendermaßen definiert ist:

struct sockaddr {
   sa_family_t sa_family; // Adressfamilie AF_XXX
   char sa_data[14];      // Protokolladresse (IP-Nr. und Portnr.)
 };

Da diese Struktur allerdings recht umständlich auszufüllen ist, wurde für IP-Anwendungen eine spezielle Struktur eingeführt, mit der es möglich ist, die IP-Nummer und die Portnummer getrennt einzutragen:

struct sockaddr_in {
   sa_family sin_family;          // Adressfamilie AF_XXX
   unsigned short int sin_port;   // Portnummer
   struct in_addr sin_addr;       // IP-Adresse
   unsigned char pad[8];          // Auffüllbytes für sockaddr
 };

Da beide Strukturen im Speicher gleichwertig sind, reicht es aus, eine einfache Typumwandlung bei connect() vorzunehmen. Mit dem letzten Parameter (addrlen) von connect() geben Sie die Länge in Bytes von sockaddr mit dem sizeof-Operator an.

Ausfüllen von »sockaddr_in«

In der Strukturvariablen sin_family geben Sie die Adressfamilie (Protokollfamilie) an, mit der Sie kommunizieren wollen. Gewöhnlich gibt man hierfür dieselbe Familie an, wie schon beim ersten Parameter der Funktion socket().

In sin_port geben Sie die Portnummer an, über die Sie mit dem Server in Kontakt treten wollen. Wichtig ist hierbei, dass Sie den Wert in der Network Byte Order angeben. Es genügt also nicht, wenn Sie sich beispielsweise mit einem Webserver verbinden wollen, als Portnummer einfach 80 hinzuschreiben. Sie müssen hierbei auch auf die verschiedenen Architekturen Rücksicht nehmen, die es in heterogenen Netzwerken gibt. Denn auf den verschiedenen Architekturen gibt es unterschiedliche Anordnungen der Bytes zum Speichern von Zahlen. So wird bei der Anordnung gewöhnlich zwischen Big Endian und Little Endian unterschieden. Man spricht dabei gern vom »Zahlendreher«. Beim Big Endian-Format 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.

Um jetzt aus einer lokal verwendeten Byte-Reihenfolge (Host Byte Order) eine Network-Byte-Order-Reihenfolge oder umgekehrt zu konvertieren, stehen Ihnen die folgenden vier 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);

Nicht jeder kennt allerdings die entsprechenden Portnummern zum entsprechenden Dienst. Hierbei kann die Funktion getservbyname() helfen. Dieser Funktion übergeben Sie den Namen eines Dienstes und das Transportprotokoll als Parameter. Anschließend sucht getservbyname() in einer speziellen Datei nach einem Eintrag, der dazu passt, und gibt die Portnummer zurück. Hierfür gibt es eine spezielle Struktur in der Headerdatei <netdb.h> , mit der Sie an die Informationen zu den entsprechenden Diensten kommen:

struct servent {
   char *s_name;      // offizieller Name vom Service
   char **s_aliases;  // Alias-Liste
   int  s_port;       // Portnummer zum Servicenamen
   char *s_proto;     // verwendetes Protokoll
};

Eine kurze Beschreibung der einzelnen Strukturvariablen:

  • s_name – offizieller Servicename
  • s_aliases – Ein Stringarray mit eventuellen Aliasnamen zum Service, falls vorhanden. Das letzte Element in der Liste ist NULL.
  • s_port – die Portnummer zum Servicenamen
  • s_proto – der Name des zu verwendenden Protokolls

Die Syntax von getservbyname() lautet:

#include <netdb.h>

struct servent *getservbyname ( const char *name,
                                const char *proto );

Wenn Sie den Dienst name und das Protokoll proto angeben, liefert Ihnen diese Funktion bei Erfolg eine Adresse auf die Information in struct servent. Bei einem Fehler wird NULL zurückgegeben.

Die IP-Adresse geben Sie in der Strukturvariablen sin_addr an. Allerdings wird auch hier die Network-Byte-Order-Reihenfolge erwartet. Hierbei ist uns allerdings die Funktion inet_addr() (oder die etwas sicherere Alternative inet_aton()) behilflich. Hierbei können Sie die IP-Adresse als String angeben und bekommen einen für sin_addr benötigten 32-Bit Wert in Network Byte Order zurück.

Wenn der Client den Dienst eines Servers verwenden will, muss jenem natürlich dessen IP-Adresse bekannt sein. Meistens gibt ein Endanwender aber als Adresse den Rechnernamen anstatt der IP-Adresse an, da dieser einfacher zu merken ist. Damit also ein Client aus dem Rechnernamen (beispielsweise www.google.de) eine IP-Adresse (216.239.59.99) erhält, wird die Funktion gethostbyname() verwendet.

#include <netdb.h>

struct hostent *gethostbyname(const char *rechnername);

Um also aus einem Rechnernamen eine IP-Adresse und weitere Informationen zu ermitteln, steht ein sogenannter Nameserver zur Verfügung – dieser Rechner ist für die Umsetzung zwischen Rechnernamen und IP-Nummern zuständig. Selbst auf Ihrem Rechner finden Sie solche Einträge der lokalen IP-Nummern in der Datei /etc/hosts hinterlegt. Im Internet hingegen werden diese Daten in einer eigenen Datenbank gehalten. Um solche Informationen zu den einzelnen Rechnern zu erhalten, ist in der Headerdatei <netdb.h> folgende Struktur definiert:

struct hostent {
   char *  h_name;
   char ** h_aliases;
   short   h_addrtype;
   short   h_length;
   char ** h_addr_list;
 };

Eine kurze Beschreibung der einzelnen Strukturvariablen:

  • h_name – offizieller Name des Rechners.
  • h_aliases – ein Stringarray, in dem sich eventuell vorhandene Aliasnamen befinden. Das letzte Element ist immer NULL.
  • h_addrtyp – Hier steht der Adresstyp, was gewöhnlich AF_INET für IPv4 ist.
  • h_length – Hier findet sich die Länge der numerischen Adresse.
  • h_addr_list – Hierbei handelt es sich um ein Array von Zeigern auf die Adressen für den entsprechenden Rechner.

Die Funktion gethostbyname() gibt bei Erfolg einen Zeiger auf struct hostent des gefundenen Rechners zurück, ansonsten bei einem Fehler NULL. Die letzte Strukturvariable pad in der Struktur sockaddr_in wird lediglich als Lückenfüller verwendet, um sockaddr_in auf die Größe von sockaddr aufzufüllen.

Wenn Sie jetzt alle Strukturvariablen der Struktur sockaddr_in mit Werten belegt haben, können Sie die Funktion connect() aufrufen und bei stehender (erfolgreicher) Verbindung Daten austauschen (senden und empfangen).

Hier folgt ein Codeausschnitt, der zeigt, wie ein »Auffüllen« der Struktur sockaddr_in und der anschließende Aufruf der Funktion connect() vonstatten geht. Im Beispiel wird versucht, sich mit einem Webserver (Port 80; HTTP) zu verbinden, dessen IP-Adresse Sie als Argument in der Kommandozeile übergeben haben.

struct sockaddr_in server;
unsigned long addr;
...
// Alternative zu memset() -> bzero()
memset( &server, 0, sizeof (server));

addr = inet_addr( argv[1] );
memcpy( (char *)&server.sin_addr, &addr, sizeof(addr));
server.sin_family = AF_INET;
server.sin_port = htons(80);
...
// Baue die Verbindung zum Server auf.
if (connect(sock,(struct sockaddr*)&server, sizeof(server)) < 0){
        // Fehler beim Verbindungsaufbau ...
    }

Hinweis

Wenn Sie UDP anstatt TCP verwenden, können Sie auf einen Aufruf von connect() verzichten. Dann allerdings müssen Sie die entsprechende Adressinformation bei den Funktionen sendto() zum Senden und recvfrom() zum Empfangen von Daten ergänzen.



Rheinwerk Computing - Zum Seitenanfang

25.4.3 Senden und Empfangen von Daten Zur nächsten ÜberschriftZur vorigen Überschrift

Nachdem Sie sich erfolgreich mit dem Server verbunden haben, können Sie anfangen, Daten an den Server zu senden bzw. Daten zu empfangen. Hierzu gibt es jeweils für TCP und UDP ein Funktionspaar. Es war ja schon einmal die Rede davon, dass man mit Sockets ähnlich wie bei Dateien mit Filedeskriptoren arbeiten kann. Und in der Tat, unter Linux/UNIX kann der Austausch von Daten über Sockets auch mit den Systemcalls read() und write() stattfinden. Allerdings ist dies unter MS-Windows erst ab den Versionen NT/2000/XP mit den Funktionen ReadFile() und WriteFile() möglich.


Hinweis

Natürlich gilt auch hier, dass die Funktionen zum Senden und Empfangen nicht nur für die Clients, sondern auch für die Serveranwendung gelten.


»send()« und »recv()« – TCP

Zum Senden von Daten von einem Socket an den Stream wird gewöhnlich die Funktion send() verwendet, die unter Linux/UNIX folgende Syntax besitzt:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send ( int socketfd, const void *data,
               size_t data_len, unsigned int flags );

Unter MS-Windows mit Winsock sieht die Syntax wieder ähnlich aus:

#include <winsock.h>

int send ( SOCKET s, const char FAR* data,
           int data_len, int flags );

Wenn Sie diese Funktion mit write() vergleichen, können Sie Parallelen ziehen. Mit dem ersten Parameter geben Sie den Socket-Deskriptor an, über den Sie die Daten senden wollen. Im zweiten Parameter wird ein Zeiger auf den Speicherbereich erwartet, in dem sich die Daten befinden. Die Größe des Speicherbereichs geben Sie mit dem dritten Parameter an. Mit dem letzten Parameter können Sie das Verhalten von send() noch beeinflussen. Wird hierbei 0 angegeben, verhält sich send() wie die Systemfunktion write() zum Schreiben. Ansonsten wäre beispielsweise die symbolische Konstante MSG_OOP ein häufig verwendeter Wert, mit dem »Out-of-band«-Daten gesendet werden können. Weitere flags entnehmen Sie bitte wieder aus der entsprechenden Dokumentation (beispielsweise der Manual-Page) – da ich hierauf nicht näher eingehe.

Im Falle eines Fehlers liefert send() –1 (was unter MS-Windows gleichwertig zur Konstante SOCKET_ERROR ist) zurück. Welcher Fehler auftrat, lässt sich wieder mit den üblichen betriebssystembedingten Routinen überprüfen (errno unter Linux/UNIX und WSAGetLastError() unter MS-Windows).

Auch wenn kein Fehler auftritt, ist es dennoch sehr wichtig, den Rückgabewert zu überprüfen. Denn bei der Netzwerkprogrammierung sind auch gewisse Grenzen (Bandbreite) vorhanden – sprich, Sie können nicht unendlich viele Daten auf einmal versenden. Mit der Auswertung des Rückgabewerts können bzw. müssen Sie sich selbst darum kümmern, dass der eventuelle Rest, der nicht gesendet werden konnte, ebenfalls noch verschickt wird. Dies erledigen Sie, indem Sie data_len mit dem Rückgabewert von send() vergleichen. Durch diese Differenz (data_lenRückgabewert) erhalten Sie die noch nicht gesendeten Daten.

Um Daten von einem Stream-Socket zu empfangen (zu lesen), wird die Funktion recv() verwendet. Die Syntax unter Linux/UNIX lautet:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv ( int socketfd, void *data ,
               size_t data_len, unsigned int flags );

Und die Syntax unter MS-Windows ist:

#include <winsock.h>

int recv (SOCKET s, char FAR* data,  int data_len,  int flags);

Auch hier lassen sich mit Ausnahme des letzten Parameters wieder Parallelen zur Systemfunktion read() ziehen. Der erste Parameter ist wieder der Socket-Deskriptor der Verbindung, gefolgt von einem Zeiger auf einen Puffer, in den die Daten gelegt werden sollen. Die Länge des Puffers geben Sie mit dem dritten Parameter an, und mit den Flags können Sie das Verhalten von recv() beeinflussen. Eine Angabe von 0 bedeutet auch hier, dass sich recv() wie die Funktion read() verhält. Ansonsten wird auch hierbei gern die Konstante MSG_OOP (für »Out-of-band«-Daten, die gelesen werden können) und MSG_PEEK verwendet. Mit MSG_PEEK können Daten erneut gelesen werden. Zu weiteren möglichen flags sollten Sie bei Bedarf die entsprechende Dokumentation lesen (beispielsweise die Manual-Page).

Im Falle eines Fehlers gilt dasselbe wie schon bei der Funktion send(). Außerdem kann die Funktion recv() auch 0 zurückgeben. Dies bedeutet dann, dass der Verbindungspartner seine Verbindung beendet hat. Ansonsten wird auch mit recv() die Anzahl der erfolgreich gelesenen Bytes zurückgeliefert.

»sendto()« und »recvfrom()« – UDP

Für die Funktionen zum Senden und Empfangen von Datagrammen (UDP-Sockets) werden vorzugsweise sendto() und recvfrom() verwendet. Die Syntax unter Linux/UNIX lautet:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t  recvfrom( int  s,  void  *buf,  size_t  len,
                   int flags, struct sockaddr *from,
                   socklen_t   *fromlen );

ssize_t  sendto( int  s,  const  void  *msg,  size_t  len,
                 int  flags, const struct sockaddr *to,
                 socklen_t tolen );

Und es gibt eine entsprechende ähnliche Syntax unter MS-Windows:

#include <winsock.h>

int sendto( SOCKET s, const char FAR * buf,  int len,
            int flags, const struct sockaddr FAR * to,
            int tolen );

int recvfrom( SOCKET s, char FAR* buf, int len,
              int flags,  struct sockaddr FAR* from,
              int FAR* fromlen );

Die Bedeutung der einzelnen Parameter sowie des Rückgabewerts entspricht exakt der von den TCP-Gegenstücken send() und recv(). Hinzugekommen hingegen sind am Ende zwei weitere Parameter. Mit dem fünften Parameter übergeben Sie einen Zeiger auf die Adresse des Zielrechners (bei sendto()) bzw. einen Zeiger auf die Adresse des Absenders (bei recvfrom()). Die Angaben entsprechen dabei dem Parameter sockaddr von der Funktion connect(). Mit dem letzten Parameter beider Funktionen geben Sie wieder die Größe der Struktur sockaddr an.

Sollten Sie bei einer UDP-Verbindung die connect()-Funktion verwenden, können Sie auch die Funktionen send() und revc() verwenden. In diesem Fall werden die fehlenden Informationen zur Adresse automatisch ergänzt.


Rheinwerk Computing - Zum Seitenanfang

25.4.4 »close()« und »closesocket()« topZur vorigen Überschrift

Sobald Sie mit der Datenübertragung fertig sind, sollten Sie den Socket-Deskriptor wieder freigeben bzw. schließen. Unter Linux/UNIX können Sie hierbei, wie beim Lesen und/oder Schreiben einer Datei, ein simples close() verwenden:

#include <unistd.h>

int close(int s);

Unter MS-Windows hingegen wird hierbei die Funktion closesocket() verwendet, die letztendlich, abgesehen von ihrem anderen Namen, dieselbe Wirkung erzielt wie ein close() unter Linux/UNIX.

#include <winsock.h>

int closesocket( SOCKET s);

Beide Funktionen erwarten als Parameter den zu schließenden Socket-Deskriptor und geben bei Erfolg 0, ansonsten bei einem Fehler –1 (gleichwertig zu SOCKET_ERROR unter MS-Windows) zurück. Auch hierbei können Sie den Fehler anhand von errno (Linux/UNIX) oder der Funktion WSAGetLastError() (MS-Windows) ermitteln. Ein Aufruf von close() bzw. closesocket() beendet außerdem eine TCP-Verbindung sofort.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück
  
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: C von A bis Z

 C von A bis Z
Jetzt bestellen


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

 Buchtipps
Zum Rheinwerk-Shop: C/C++






 C/C++


Zum Rheinwerk-Shop: Einstieg in C






 Einstieg in C


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


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




Copyright © Rheinwerk Verlag GmbH 2009
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