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

 << zurück
C von A bis Z von Jürgen Wolf
Das umfassende Handbuch für Linux, Unix und Windows
– 2., aktualisierte und erweiterte Auflage 2006
Buch: C von A bis Z

C von A bis Z
1.116 S., mit CD, Referenzkarte, 39,90 Euro
Galileo Computing
ISBN 3-89842-643-2
gp Kapitel 28 Netzwerkprogrammierung und Cross-Plattform-Entwicklung
  gp 28.1 Begriffe zur Netzwerktechnik
    gp 28.1.1 IP-Nummern
    gp 28.1.2 Portnummer
    gp 28.1.3 Host- und Domainname
    gp 28.1.4 Nameserver
    gp 28.1.5 Das IP-Protokoll
    gp 28.1.6 TCP und UDP
    gp 28.1.7 Was sind Sockets?
  gp 28.2 Headerdateien zur Socketprogrammierung
    gp 28.2.1 Linux/UNIX
    gp 28.2.2 Windows
  gp 28.3 Client-/Server-Prinzip
    gp 28.3.1 Loopback-Interface
  gp 28.4 Erstellen einer Client-Anwendung
    gp 28.4.1 socket() – Erzeugen eines Kommunikationsendpunktes
    gp 28.4.2 connect() – Client stellt Verbindung zum Server her
    gp 28.4.3 Senden und Empfangen von Daten
    gp 28.4.4 close(), closesocket()
  gp 28.5 Erstellen einer Server-Anwendung
    gp 28.5.1 bind() – Festlegen einer Adresse aus dem Namensraum
    gp 28.5.2 listen() – Warteschlange für eingehende Verbindungen einrichten
    gp 28.5.3 accept() und die Serverhauptschleife
  gp 28.6 (Cross-Plattform)TCP-Echo-Server
    gp 28.6.1 Der Client
    gp 28.6.2 Der Server
  gp 28.7 Cross-Plattform-Development
    gp 28.7.1 Abstraktion Layer
    gp 28.7.2 Headerdatei Linux/UNIX
    gp 28.7.3 Linux/UNIX-Quelldatei
    gp 28.7.4 Headerdatei MS-Windows
    gp 28.7.5 Windows-Quelldatei
    gp 28.7.6 All together – die main-Funktionen
    gp 28.7.7 Ein UDP-Beispiel
    gp 28.7.8 Mehrere Clients gleichzeitig behandeln
  gp 28.8 Weitere Anmerkungen zur Netzwerkprogrammierung
    gp 28.8.1 Das Datenformat
    gp 28.8.2 Der Puffer
    gp 28.8.3 Portabilität
    gp 28.8.4 Von IPv4 nach IPv6
    gp 28.8.5 RFC-Dokumente (Request for Comments)
    gp 28.8.6 Sicherheit


Galileo Computing - Zum Seitenanfang

28.5 Erstellen einer Server-Anwendung  downtop

Die Erstellung der Server-Anwendung gestaltet sich nicht viel schwieriger, als die der Client-Anwendung. Der Datenaustausch erfolgt genauso wie bei der Client-Anwendung via send()/recv() (TCP) bzw. sendto()/recvfrom() (UDP). Der Server muss allerdings keine Verbindung herstellen – dies ist die Aufgabe vom Client. Allerdings ist es die Aufgabe des Servers, Verbindungswünsche anzunehmen. Und um dies zu realisieren, müssen Sie den Server in einen Wartezustand versetzen.


Galileo Computing - Zum Seitenanfang

28.5.1 bind() – Festlegen einer Adresse aus dem Namensraum  downtop

Nachdem Sie auch auf der Serverseite mit der Funktion socket() eine »Steckdose« bereitgestellt haben, müssen Sie zunächst die Portnummer der Server-Anwendung festlegen. Sie wissen ja bereits von der Clientanwendung, dass mittels connect() auf eine bestimmte IP-Adresse und eine Portnummer des Servers zugegriffen wird. Unter welcher IP-Adresse und Portnummer, der Server denn nun auf Anfragen der Clients wartet, müssen Sie mit der Funktion bind() festlegen. Somit weisen Sie praktisch einem Socket eine Adresse zu – schließlich ist es durchaus gängig, dass eine Serveranwendung mehrere Sockets verwendet. Dass hierbei meistens die IP-Adresse die gleiche ist, dürfte klar sein, aber es ist durchaus möglich die Datenübertragung über mehrere Ports zuzulassen. Die Funktion bind() wiederum teilt dem Betriebssystem mit, welchen Socket es mit einem bestimmten Port verknüpfen soll. Sobald dann ein Datenpaket eingeht, erkennt das Betriebssystem anhand der Portnummer, für welchen Socket das Paket ist.

Hierzu die Syntax zur Funktion bind() bei Linux/UNIX:

#include <sys/types.h>
#include <sys/socket.h>
int bind( int s, const struct sockaddr name, int namelen );

Und die ähnliche Syntax bei MS-Windows:

#include <winsock.h>
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);

Als ersten Parameter übergeben Sie, wie immer, den Socket-Deskriptor, den Sie mit socket() angelegt haben. Mit dem zweiten Parameter geben Sie einen Zeiger auf eine Adresse und Portnummer an. Damit teilen Sie dem System mit, welche Datenpakete für welches Socket gedacht sind. Die Struktur sockaddr bzw. (einfacher) sockaddr_in und deren Mitglieder wurde bereits ausführlich im Abschnitt zur Funktion connect() beschrieben. Allerdings sollte hier noch erwähnt werden, dass ein Rechner häufig über verschiedene Rechner (unter mehreren Adressen) und auch verschiedenste Netze (Internet, Intranet, lokales Netzwerk, etc.) erreichbar ist bzw. sein muss. Damit ein Server über alle Netze und IP-Adressen eine Verbindung annimmt, setzt man die IP-Adresse auf INADDR_ANY (natürlich im Network Byte Order). Ansonsten geben Sie die IP-Adresse wie gewöhnlich mit der Funktion inet_addr() an.

Neben der IP-Adresse ist es außerdem auch möglich, eine beliebige Portnummer zuzulassen. Hierfür müssen Sie lediglich 0 als Portnummer (im Network Byte Order) verwenden. Welchen Port Sie dann erhalten haben, können Sie mit der Funktion getsockname() im Nachhinein abfragen. Mehr zu dieser Funktion können Sie aus der entsprechenden Dokumentation entnehmen (bspw. Manual-Page).

Mit dem letzten Parameter geben Sie wiederum die Länge der Struktur (zweiter Parameter) in Bytes mit sizeof() an. bind() liefert im Falle eines Fehlers –1 (gleichwertig mit dem Fehlercode SOCKET_ERROR unter MS-Windows). Welcher Fehler aufgetreten ist, können Sie wiederum mit errno (Linux/UNIX) bzw. WSAGetLastError() (MS-Windows) in Erfahrung bringen.

Hierzu ein kurzer Codeausschnitt, wie die Zuweisung einer Adresse auf der Serverseite in der Praxis realisiert wird:

struct sockaddr_in server;
memset( &server, 0, sizeof (server));
// IPv4-Adresse
server.sin_family = AF_INET;
// Jede IP-Adresse ist gültig
server.sin_addr.s_addr = htonl( INADDR_ANY );
// Portnummer 1234
server.sin_port = htons( 1234 );
if(bind( sock, (struct sockaddr*)&server, sizeof( server)) < 0) {
   //Fehler bei bind()
 }

Galileo Computing - Zum Seitenanfang

28.5.2 listen() – Warteschlange für eingehende Verbindungen einrichten  downtop

Im nächsten Schritt müssen Sie eine Warteschlange einrichten, welche auf eingehende Verbindungswünsche eines Clients wartet – man spricht auch gerne vom »horchen« am Socket auf eingehende Verbindungen. Eine solche Warteschlange wird mit der Funktion listen() eingerichtet. Dabei wird die Programmausführung des Servers solange unterbrochen, bis ein Verbindungswunsch eintrifft. Mit listen() lassen sich durchaus mehrere Verbindungswünsche »gleichzeitig« einrichten. Die Syntax dieser Funktion sieht unter Linux/UNIX wie folgt aus:

#include <sys/types.h>
#include <sys/socket.h>
int listen( int s, int backlog );

Unter MS-Windows hingegen sieht die Syntax wie folgt aus:

#include <winsock.h>
int listen( SOCKET s, int backlog );

Mit dem ersten Parameter geben Sie wie immer den Socket-Deskriptor an und mit dem zweiten Parameter die Länge der Warteschlange. Die Länge der Warteschlange ist die maximale Anzahl von Verbindungsanfragen, die in eine Warteschlange gestellt werden, wenn keine Verbindungen mehr angenommen werden können.

Der Rückgabewert ist bei Erfolg 0 und auch hier bei einem Fehler –1 (gleich bedeutend unter MS-Windows mit SOCKET_ERROR). Den Fehlercode selbst können Sie wieder wie gehabt mit den errno (Linux/UNIX) bzw. WSAGetLastError() (MS-Windows) auswerten.

In der Praxis sieht die Verwendung von listen() wie folgt aus:

if( listen( sock, 5 ) == –1 ) {
   // Fehler bei listen()
}

Galileo Computing - Zum Seitenanfang

28.5.3 accept() und die Serverhauptschleife  toptop

Sobald nun ein oder mehrere Clients Verbindung mit dem Server aufnehmen wollen, können Sie sich darauf verlassen, dass die Funktion accept() immer die nächste Verbindung aus der Warteschlange holt (die Sie mit listen() eingerichtet haben). Hier die Syntax dazu unter Linux/UNIX:

#include <sys/types.h>
#include <sys/socket.h>
int accept( int s, struct sockaddr *addr, socklen_t addrlen );

Und eine ähnliche Syntax unter MS-Windows:

#include <winsock.h>
SOCKET accept( SOCKET s,
               struct sockaddr FAR* addr,
               int FAR* addrlen );

An der Syntax unter MS-Window lässt sich gleich erkennen, dass die Funktion accept() als Rückgabewert einen neuen Socket zurückgibt. Hierbei handelt es sich um den gleichen Socket mit denselben Eigenschaften, wie vom ersten Parameter s.  Über diesen neuen Socket wird anschließend die Datenübertragung der Verbindung abgewickelt. Ein so akzeptiertes Socket kann allerdings nicht mehr für weitere Verbindungen verwendet werden. Das Orginalsocket s hingegen bleibt weiterhin für weitere Verbindungswünsche offen.


Hinweis   accept() ist eine blockierende Funktion – dass heißt, accept() blockiert den aufrufenden (Server-)Prozess solange, bis eine Verbindung vorhanden ist. Sofern Sie die Eigenschaften des Socket-Deskriptors auf nicht-blockierend ändern, gibt accept() einen Fehler zurück, wenn beim Aufruf keine Verbindungen vorhanden sind.


In den zweiten Parameter schreibt accept() Informationen (IP-Adresse und Port) über den Verbindungspartner in die Struktur sockaddr bzw. sockaddr_in. Dies ist logischerweise nötig, damit Sie wissen, mit wem Sie es zu tun haben. addrlen wiederum ist die Größe der Struktur sockaddr bzw. sockaddr_in – allerdings wird diesmal ein Zeiger auf die Größe der Adresse erwartet!

Bei einem Fehler wird –1 (gleich bedeutend mit SOCKET_ERROR unter MS-Windows) zurückgegeben. Die genaue Ursache des Fehlers können Sie wieder mit errno (Linux/UNIX) bzw. WSAGetLastError() (MS-Windows) ermitteln. Bei erfolgreicher Ausführung von accept() wird, wie bereits beschrieben, ein neuer Socket-Deskriptor zurückgegeben.

Ein wichtiger Teil der Serverprogrammierung ist außerdem die Serverhauptschleife. In dieser Schleife wird gewöhnlich die Funktion accept() aufgerufen und darin findet auch gewöhnlich der Datentransfer zwischen Client und Server statt. Hier ein Beispiel einer solchen Serverhauptschleife:

struct sockaddr_in client;
int sock, sock2;
socklen_t len;
...
for (;;) {
        len = sizeof( client );
        sock2 = accept( sock, (struct sockaddr*)&client, &len);
        if (sock2 < 0) {
            //Fehler bei accept()
        }
        // Hier beginnt der Datenaustausch
    }

Hierzu nochmals der bildliche Vorgang aller Socket-Funktionen für eine TCP-Verbindung zwischen dem Server und dem Client (siehe Abbildung 28.2).

Selbiger bildlicher Vorgang aller Socket-Funktionen für eine UDP-Verbindung zwischen Server und Client sieht hingegen so aus (siehe Abbildung 28.3).

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 28.2   Kompletter Vorgang einer TCP-Client/Server-Verbindung

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 28.3   Kompletter Vorgang einer UDP-Client/Server-Verbindung

 << zurück
  
  Zum Katalog
Zum Katalog: C von A bis Z
C von A bis Z
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Shell-Programmierung






 Shell-Programmierung


Zum Katalog: Linux-UNIX-Programmierung






 Linux-UNIX-Programmierung


Zum Katalog: C/C++






 C/C++


Zum Katalog: UML 2.0






 UML 2.0


Zum Katalog: Reguläre Ausdrücke






 Reguläre Ausdrücke


Zum Katalog: Linux






 Linux


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo





Copyright © Galileo Press 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.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de