SUCHE MIT Google
Web virtualuniversity.ch
HOME DIDAKTIK ECDL ELEKTRONIK GUIDES HR MANAGEMENT MATHEMATIK SOFTWARE TELEKOM
DIENSTE
Anmeldung
Newsletter abonnieren
Sag's einem Freund!
VirtualUniversity als Startseite
Zu den Favoriten hinzufügen
Feedback Formular
e-Learning für Lehrer
Spenden
Autoren login
KURSE SUCHEN
Kurse veröffentlichen

Suche nach Datum:

Suche mit Schlüsselwort:

Suche nach Land:

Suche nach Kategorie:
PARTNER
ausbildung24.ch - Ausbildungsportal, Seminare, Kursen... 

 
HTMLopen.de - Alles was ein Webmaster braucht

 
PCopen.de - PC LAN Netze und Netzwerke - alles was ein IT Profi und Systemtechnicker braucht

TELEKOM

TCP/IP-Sockets: Die Funktionen

Da die Programmierung von Client und Server primär in Perl stattfinden wird,erfolgt die Vorstellung der Systemfunktionen recht kurz. Die Ähnlichkeitder später besprochenen Perl-Funktionen und -Methoden mit den C-Systemaufrufeninst jedoch nicht rein zufällig.

Kommunikationsendpunkt: socket

Um mit Sockets zu arbeiten, muß zuerst eine Verbindung geöffnetwerden. Hier gibt es Analogien zu Dateizugriffen. Der Aufruf socket entspricht einemfopen bei Dateien. Die Funktion socket() hat drei Parameter:
int socket(int Family, int Sockettype, int Protocol);
  • Family legt die Protokoll-Familie fest:
    • AF_UNIX   : UNIX-interne Protokolle
    • AF_INET   : Internet-Protokolle
    • AF_NS     : Xerox-NS-Protokolle
    • AF_IMPLINK: IMP-Link-Schicht
    Wir werden nur mit AF_INET zu tun haben.

  • Sockettype legt den Typ des Sockets (und damit auch teilweise das Protokoll) fest (in Klammern die Anwendung für die Familie AF_INET):
    • SOCK_STREAM   : Stream-Socket (TCP)
    • SOCK_DGRAM    : Datagramm-Socket (UDP)
    • SOCK_RAW      : Raw-Socket (IP)
    • SOCK_SEQPACKET: Paket-Socket
    • SOCK_RDM      : Nachrichten-Socket

  • Protocol legt das genaue Protokoll fest. Für AF_INET sind die möglichen Werte:
    • IPPROTO_UDP : UDP-Protokoll (SOCK_DGRAM)
    • IPPROTO_TCP : TCP-Protokoll (SOCK_STREAM)
    • IPPROTO_ICMP: ICMP-Protokoll (SOCK_RAW)
    • IPPROTO_RAW : Raw-IP-Protokoll (SOCK_RAW)

Der socket()-Systemaufruf liefert einen Integerwert zurück, der einem Dateidescriptor ähnelt. Dieser Wert wird daher "Socketdeskriptor" oder "sockfd" genannt. Im Fehlerfall hat er den Wert -1. Beispiel:

                                       #include <sys/types.h>
                                       #include <sys/socket.h>
                                       
                                       int MySocket, ForeignSocket;
                                       ...
                                       
                                       MySocket = socket(AF_INET, SOCK_STREAM, IPPPROTO_TCP);
                                       ...
                                       
                                       close(MySocket);
                                       
Nach Aufruf von "socket" ist der Socket jedoch noch nicht betriebsbereit. Es muss jetzt noch festgelegt werden, für welchen Port (d.h. für welches Protokoll der Anwendungsebene) der Socket zuständig sein soll, ob es sich um einen Server- oder Client-Socket handeln soll, etc.

Jeder eröffnete Socket muß auch wieder geschlossen werden. Eine Nachlässigkeit an dieser Stelle kann sich bitter rächen, da insbesondere bei Serverprozessen Verbindungen sehr oft eröffnet werden und die Systemresourcen für Netzverbindungen irgendwann zur Neige gehen, was meist zum Stillstand des Servers führt. Das Schließen des Sockets erfolgt unter UNIX mit dem close-Aufruf.

Socket einrichten: bind

Der Serverprozess muß von außen erreichbar sein. Dazu bekommt er einen sogenannten well known port. Diese Nummer ist also den Clientprozessen bekannt. Um einen Socket an diese Nummer zu binden, wird der bind-Aufruf verwendet. Als Parameter verwendet bind den Socket und eine Struktur sockaddr_in, die diesen Port beschreibt.
                                       int bind (int sockfd, struct sockaddr *Myaddr, int Addrlen);
                                       
Mit dem Aufruf wird ein Speicherbereich bereitgestellt, der zur Festlegung der Protokoll-Familie und der Portnummer vorgesehen ist. Bei einem Server-Socket erfolgt damit die Zuordnung zu dem gewünschten Port - er erklärt sich damit zuständig für ein bestimmtes Anwender-Protokoll. Der Aufruf von "bind" ist sowohl bei Datenströmen als auch bei Datagrammen erforderlich. Der Parameter "sockfd" ist ein Dateideskriptor, der mit einem vorangegangenen "socket"-Aufruf erzeugt wurde. Der zweite Parameter ist ein Zeiger auf eine protokollspezifische Adresse und der dritte Parameter gibt die Größe der Adreßstruktur an. bind wird in drei Fällen angewendet:
  1. Server registrieren ihre eigene Adresse innerhalb des Systems.
  2. Ein Client kann eine spezifische Adresse selbst speichern.
  3. Ein verbindungsloser Client muß vom System eine individuelle Adresse anfordern, damit er eine gültige Adresse für die Rückantwort hat.
bind füllt also im oben angeführten Fünfertupel die Felder "lokale Adresse" und "lokaler Prozeß".

struct sockaddr ist eine allgemeingültige Datenstruktur, die für verschieden Protokollfamilien existiert. Bei Verwendung der Internet-Protokollfamilie kann sie vom Anwenderprogramm überlagert werden durch eine Struktur sockaddr_in, die ausschließlich für IP geeignet ist. Unter der Annahme, daß der Anwender eine Variable vom Typ "sockaddr_in" in der Form struct sockaddr_in adresse; deklariert hat, enthält "adresse" u. a. die folgenden Komponenten:

                                       adresse.sin_family      /* vorzeichenlose 16bit-Ganzzahl (Protokoll-Familie) */
                                       adresse.sin_port        /* vorzeichenlose 16bit-Ganzzahl (Portnummer) */
                                       adresse.sin_addr.s_addr /* vorzeichenlose 32bit-Ganzzahl (Internetadresse) */
                                       
In die Komponente sin_family wird die Konstante AF_INET (2) eingetragen. In die Komponente sin_port ist die Portnummer einzutragen - allerdings in sog. "Netzwerk-Anordnung": Portnummer und Internetadresse sind Zahlen, die über das Netz verschickt werden und demnach unabhängig von der internen Zahlendarstellung des jeweiligen Rechners sein müssen (siehe später).

                                       struct sockaddr_in adresse;
                                       
                                       adresse.sin_family = AF_INET; /* Internet-Protokoll-Familie */
                                       adresse.sin_port = htons(80); /* Port festlegen */
                                       adresse.sin_addr.s_addr = 0;  /* Internetadresse irrelevant */
                                       
                                       int ergebnis = bind(descriptor,(struct sockaddr *)&adresse,sizeof(adresse));
                                       
Der "Typecast"-Operator (struct sockaddr *) ist erforderlich, wenn der Compiler auf strenge Typprüfung eingestellt ist. Die Funktion bind() erwartet ja einen Zeiger vom Typ struct sockaddr *.

Warteschlange festlegen: listen

Der listen-Aufruf gibt an, wieviele Anfragen gepuffert werden können. In fast allen Programmen wird hier ein Wert von 5 verwendet (der derzeitige Höchstwert).
                                       int listen(int sockfd, int backlog);
                                       
listen folgt normalerweise nach socket und bind und unmittelbar vor accept.

Falls die listen()-Warteschlange voll ist, werden weitere Verbindungswünsche von Clients abgewiesen. Ein Server für Datagramme (UDP) braucht listen() nicht aufzurufen, da er keine Verbindungen zu Clients einrichtet.

Verbindungswunsch entgegennehmen: accept

Der accept-Aufruf wartet auf eine Anfrage eines Clients. Der Aufruf von accept() liefert als Rückgabewert die Socket-ID des Partners. Des weiteren wird per Parameter in einer Variablen der Struktur sockaddr_in die Adresse des Partners geliefert.
                                       int accept(int sockfd, struct sockaddr_in *Peer, int *Addrlen);
                                       
accept nimmt die erste Anforderung von der Warteschlange und generiert einen weiteren Socket mit der gleichen Eigenschaft wie sockfd. Der Parameter Peer verweist auf einen Speicherbereich, dessen Inhalt beim Aufruf undefiniert sein kann. In diesen trägt accept() die Internetadresse des Absenders eines eintreffenden Verbindungswunsches ein (die Adresse des Clients). Auf diese Datenstruktur kann genauso zugegriffen werden, wie dies bereits bei bind() erklärt wurde. Peer und Addrlen liefern also die Felder "ferne Adresse" und "ferner Prozeß" des Fünfertupels. Der Parameter "Addrlen" ist die Adresse eines Variablen-Parameters: Vor dem Aufruf muß dort die maximale Länge des Speicherbereiches stehen, auf den der Parameter Peer zeigt. Nach dem Aufruf enthält er die Anzahl Bytes, die das Betriebssystem tatsächlich dort eingetragen hat.

accept generiert (bei einem concurrent server) automatisch einen neuen Socketdescriptor für die aktuelle Verbindung. Der Rückgabewert von accept() ist also ein neuer Dateideskriptor, über den in der Folge die Kommunikation mit dem Client erfolgt (z.B. mit read() und write()). Der als erster Parameter angegebene Deskriptor bleibt für weitere Verbindungswünsche reserviert. Zum Beispiel:

                                       #include <sys/types.h>
                                       #include <sys/socket.h>
                                       
                                       int MySocket, ForeignSocket, Partnerlen;
                                       struct sockaddr_in AdrMySock, AdrPartnerSocket;
                                       ...
                                       
                                         MySocket = socket(AF_INET, SOCK_STREAM, IPPPROTO_TCP);
                                       ...
                                       
                                         AdrMySock.sin_family = AF_INET;
                                         AdrMySock.sin_addr.s_addr = INADDR_ANY; /* akzept. jeden */
                                         AdrMySock.sin_port = PortNr;	/* wird per getservbyname bestimmt */
                                         bind(MySocket, &AdrMySock, sizeof(AdrMySock));
                                         listen(IDMySock, 5);
                                         for(;;)
                                           {
                                           ForeignSocket = accept(MySocket, &AdrPartnerSocket, &Partnerlen);
                                           ...
                                           close(ForeignSocket);
                                           }
                                       ...
                                       
Nicht vergessen: ForeignSocket muß nach Ende der Kommunikation geschlossen werden, sonst gehen dem System nach einiger Zeit die Sockets aus.

Clientaufruf: connect

Sobald der Server läuft, kann der Client Verbindung zum well known port des Servers aufnehmen. Der entsprechende Aufruf lautet connect.
                                       int connect(int sockfd, struct sockaddr_in *ServAddr, int Addrlen);
                                       
Der Parameter sockfd ist natürlich wieder der Socket-Deskriptor. Die weiteren Parameter entsprechen jenen von bind. Für die meisten verbindungsorientierten Protokolle richtet connect eine Verbindung vom lokalen zum fernen Rechner ein.
Allerdings muß diesmal in der Datenstruktur, auf die ServAddr zeigt, die Internetadresse des gewünschten Servers eingetragen werden. Die Verbindung erfolgt zu dem angegebenen Rechner. Weiterhin ist ein Port anzugeben. Der Ziel-Server wird durch seine IP-Nummer festgelegt. Diese steht in der Struktur sockaddr_in im Element sin_addr. Beispiel:
                                       #include <sys/types.h>
                                       #include <sys/socket.h>
                                       
                                       struct sockaddr_in AdrSock;
                                       ...
                                         AdrSock.sin_family = AF_INET;
                                         AdrSock.sin_addr = HostID;
                                         AdrSock.sin_port = htons(PortNr);
                                         connect(MySocket, (struct sockaddr *)&AdrSock, sizeof(AdrSock));
                                       ...
                                       

HostID stellt eine 32-Bit-Ganzzahl dar. Im Normalfall liegt die Host-Adresse natürlich nicht Ganzzahl vor, sondern als Zeichenkette der Form "www.netzmafia.de" oder "192.168.234.77". Zu korrekten Umwandlung in eine ganze Zahl, die in Netzwerk-Anordnung vorliegen muss, stehen in der C-Bibliothek zwei Routinen zur Verfügung, gethostbyname() und inet_addr(), die weiter unten besprochen werden.

Das folgende Beispiel zeigt den kompletten Verbindungsaufbau eines Clients:

                                       #include <sys/types.h>
                                       #include <sys/socket.h>
                                       
                                       struct sockaddr_in server;
                                       
                                       /* Deklaration des Sockets */
                                       int descr = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
                                       
                                       /* Datenstruktur "server" vorbereiten */
                                       server.sin_family = AF_INET;    /* Internet-Protokoll-Familie */
                                       server.sin_port = htons(80);    /* Port 80 festlegen (HTTP) */
                                       
                                       /* 1.Versuch die Internetadresse zu ermitteln, Form: "a.b.c.d" */
                                       server.sin_addr.s_addr = inet_addr(server_name);
                                       if (server.sin_addr.s_addr == -1) /* keine korr. Punktnotation */
                                         {
                                         /* 2. Versuch: symbolisch */
                                         struct hostent *host = gethostbyname(server_name);
                                         if (host != NULL)
                                           {
                                           server.sin_addr.s_addr = *((unsigned long*)host->h_addr_list[0]);
                                           }
                                         else
                                           { 
                                           printf("Internetadresse nicht gefunden\n");
                                           exit(1);
                                           }
                                         }
                                       
                                       /* jetzt kann verbunden werden */
                                       int i = connect(descr, (struct sockaddr *)&server, sizeof(server));
                                       if (i == 0)
                                         { /* jetzt steht die Verbindung! */
                                         ...
                                         }
                                       

Datenaustausch: send, sendto, recv und recvfrom

Mit diesen Aufrufen werden Daten über die bestehenden Verbindungen transportiert. Unter UNIX könnten dafür auch die Dateiaufrufe read und write verwendet werden.
                                       int send(int sockfd, char *Buffer, int NBytes, int Flags);
                                       
                                       int sendto(int sockfd, char *Buffer, int NBytes, int Flags,
                                                  struct sockaddr_in *To, int AddrLen);
                                       
                                       int recv(int sockfd, char *Buffer, int NBytes, int Flags);
                                       
                                       int recvfrom(int sockfd, char *Buffer, int NBytes, int Flags,
                                                  struct sockaddr_in *From, int AddrLen);
                                       
                                       
Die ersten drei Parameter dieser vier Systemaufrunfe sind den ersten drei Parametern von read und write ähnlich. Der Parameter sockfd identifiziert wieder den gewünschten Socket, Buffer ist ein Zeiger auf einen beliebigen Speicherpuffer, NBytes bestimmt die Anzahl der zu übertragenden Bytes und Flags hat im Normalfall den Wert Null oder er stellt das das Resultat einer Oder-Verknüpfung mit einer der folgenden Konstanten dar:
  • MSG_OOB      : Sende/empfange Out-of-Band-Daten
  • MSG_PEEK     : Peek auf ankommende Nachricht (recv, recvfrom)
  • MSG_DONTROUTE: Leite Routing um (send, sendto)
Wird Flags beispielsweise auf den Wert 1 gesetzt (MSG_OOB), dann soll die Übertragung "out of band" erfolgen. Bei dieser Übertragung werden nach Möglichkeit bisher bereits abgeschickte Daten überholt. Es handelt sich dann beispielsweise um hochpriore Informationen, wie beispielsweise das Abbruchsignal Crtl-C beim Telnet-Protokoll.

Alle Funktionen liefern als Rückgabewert die Größe der empfangenen bzw. gesendeten Datenmenge. Die recv-Funktion liefert die Sendung in Blöcken von maximal 1 KByte Größe. Wurden größere Pakete verschickt, müssen sie stückweise gelesen werden. Das Senden ist nicht beschränkt. Da der Rückgabewert nichts über die Grösse des tatsächlich gesendeten Pakets aussagt, muß dies vom Programm geregelt werden. Wenn die Pakete nicht immer gleiche Größe besitzen, wird die Paketlänge meist in den ersten Bytes des ersten Paketes kodiert.

Für den Normalfall (Flags gleich Null) kann statt send() auch die Systemfunktion write verwendet werden. Zum Beispiel kann statt

                                       send(sock,"Hello World",11,0);
                                       
auch wie folgt programmiert werden:
                                       write(sock,"Hello World",11);
                                       
Darüberhinaus besteht natürlich die Möglichkeit, eine Datei für Standard-Ein/Ausgabe über dem betreffenden Deskriptor zu definieren:
                                       FILE *f = fdopen(descr,"rw");
                                       
wodurch nun auch mit Routinen der "stdio"-Bibliothek auf den Socket zugegriffen werden kann, z. B.:
                                       fprintf(f,"Hello World");
                                       
Dies ist insbesondere wichtig, wenn die Standardeingabe oder Standardausgabe eines beliebigen Programmes auf einen Socket umgeleitet werden soll.

Der Rückgabewert von recv() gibt Auskunft über die tatsächliche Anzahl empfangener Bytes. Ist dieser Wert -1, handelt es sich um einen Fehler, beim Wert 0 wurde die Verbindung von der Gegenseite geschlossen. Andernfalls ist der Wert immer größer 0 und kleiner gleich dem Parameter NBytes.
Bei Verwendung des Flags MSG_PEEK während des Empfangs werden die Daten zwar zum Anwenderprogramm übertragen, sie verbleiben jedoch auch noch in der Empfangswarteschlange, so daß sie mit einem nachfolgenden recv()-Aufruf nochmals gelesen werden können.
Ruft der Empfänger die recv()-Funktion mit NBytes > 0 auf und stehen im Empfangspuffer bereits Daten bereit (aber weniger als erwartet - beispielsweise weil der Rest noch nicht angekommen ist), dann kehrt die Funktion trotzdem sofort zurück und übergibt die tatsächliche Anzahl der übertragenen Bytes. Erfordert es die Logik des Anwenderprogrammes, daß vor einer Fortsetzung die Gesamtzahl der erwarteten Bytes eingetroffen ist, so muß der Aufruf von recv() so lange wiederholt werden, bis alle Daten eingetroffen sind. Die Daten müssen vom Empfänger in geeigneter Form zusammengesetzt werden.
Wird recv() mit Flags = 0 aufgerufen, kann stattdessen die Systemfunktion read() verwendet werden.

Socket schließen: close

Eine bidirektionale Socket-Verbindung kann mit dem Aufruf
                                       int shutdown(int sockfd, int how);
                                       
geschlossen werden. Dabei legt der Parameter how fest, ob künftig keine Daten mehr empfangen werden sollen (how=0), keine mehr gesendet werden (how=1), oder beides (how=2). Wird statt shutdown() die Systemfunktion close() benutzt, dann entspricht dies einem shutdown(sock,2).
                                       int close(int sockfd);
                                       

Zahlenformat: ntoh und hton

Portnummer und Internetadresse sind Zahlen, die über das Netz verschickt werden und demnach unabhängig von der internen Zahlendarstellung des jeweiligen Rechners sein müssen. Die Reihenfolge der Bytes eines Datenwortes ist auf den verschiedenen Computern unterschiedlich definiert. So besteht eine Variable vom Typ short aus zwei Byte. Auf einer Maschine mit Intel-Architektur kommt dabei das niederwerte Byte zuerst ("little endian"), während es auf einem 68000-Prozessor oder einer Sun genau umgekehrt ist. Aus diesem Grund wurde eine eindeutige Netzwerk-Anordnung der zu übertragenden Bytes definiert (höherwertige Bytes zuerst!).

Bei Rechnerarchitekturen, bei denen der Speicher nach der Host-Order ausgewertet wird (das niederwertige Byte also vor dem höherwertigen im Speicher steht), ist es notwendig, alle Werte mit dem Type LONG oder WORD vor der Übergabe an den Treiber in die Network-Order zu konvertieren. Um Zahlen der Maschine in die passende Form für das Netz zu bringen und die Programme portabel zu halten, gibt es die Makros ntoh() (Net to Host) und hton() (Host to Net). Beide wirken auf short-Variablen. Für long-Variablen gibt es die analog funktionierenden Makros htonl() und ntohl(). Vorsicht ist auch bei Vergleichen geboten: Sie liefern in Network- und Host-Order nicht das gleiche Ergebnis!

  • unsigned long int htonl(unsigned long int hostlong); wandelt eine lange Ganzzahl (32bit) von der Rechner-Anordnung ("host order") in die Netzwerk-Anordnung um.
  • unsigned short int htons(unsigned short int hostshort); wandelt eine kurze Ganzzahl (16bit) von der Rechner-Anordnung ("host order") in die Netzwerk-Anordnung um.
  • unsigned long int ntohl(unsigned long int netlong); wandelt eine lange Ganzzahl (32bit) von der Netzwerk-Anordnung ("host order") in die Rechner-Anordnung um.
  • unsigned short int ntohs(unsigned short int netshort); wandelt einekurze Ganzzahl (16bit) von der Netzwerk-Anordnung ("host order") in die  Rechner-Anordnung um.

Das Socket-Interface stellt hier eine Reihe von Konvertierungs-Funktionen zur Verfügung. Die Funktionen inet_addr() und inet_ntoa() weichen etwas von den anderen ab. Sie wandeln eine Internetadresse, die als String im "dotted quad"-Format vorliegt, in einen 32-Bit-Wert und umgekehrt.

Um beispielsweise den Port des POP3-Dienstes (110) numerisch an die Struktur sock_add_in zu übergeben, würde man hton verwenden (eigentlich sollte man dazu getservbyname verwenden):

                                       struct sockaddr_in AdrSock;
                                       ...
                                          AdrSock.sin_port = hton(110);
                                       ...
                                       

Byte-Operationen

In den verschiedenen Socket-Adreßstrukturen existieren unterschiedliche Byte-Felder, die alle behandelt werden müssen. Einige dieser Felder sind, wie auch immer, keine C-Integer-Felder, so daß hier andere Techniken angewandt werden müssen, um mit ihnen allen gleich operieren zu können. BSD definiert die folgenden drei Routinen, die auf benutzerdefinierten Byte-Strings basieren. Darunter ist zu verstehen, daß es sich um keine Standard-Strings in C handelt, die bekanntermaßen mit einem Nullbyte abgeschlossen werden, sondern die benutzerdefinierten Byte-Strings können innerhalb des Strings durchaus Nullbytes besitzen. Deshalb muß die Länge des Strings den Funktionen als Parameter mitgegeben werden.
                                       bcopy (char *Src, char *Dest, int NBytes);
                                       
Kopiert NBytes vom Ursprung (SRC) zum Ziel (Dest). Achtung: Parameterreihenfolge anders als bei strcpy.
                                       bzero (char *Dest, int NBytes);
                                       
Schreibt NBytes Null-Bytes an das angegebene Ziel.
                                       int bcmp (char *Ptrl, char *Ptr2, int NBytes);
                                       
vergleicht zwei Byte-Strings. der Rückgabewert ist gleich Null, wenn beide Byte-Strings gleich sind, sonst ungleich Null (also auch anders als bei strcmp).

Namensauflösung

Computer und Dienste werden unter TCP/IP immer über die IP-Nummern angesprochen. Für den Menschen ist jedoch ein (Domain-)Name bequemer. Allerdings gibt es für beides Mechanismen zur Namensauflösung. Im Programm ruft man entsprechende Funktionen auf.

Normalerweise sind der gewünschte Dienst und der Name des Hosts bekannt, der bezüglich des Dienstes angesprochen werden soll. Daher zuerst ein Blick auf den Host.

                                       #include <netdb.h>
                                       ...
                                       
                                       struct hostent *gethostbyname (char *hostname);
                                       ...
                                       
Die gethostbyname-Funktion gibt einen Zeiger auf eine hostent-Struktur zurück:
                                       struct hostent 
                                         {
                                         char *h_name;       /* official name of host */
                                         char **h_aliases;   /* alias list */
                                         int h_addrtype;     /* host address type */
                                         int h_length;       /* length of address */
                                         char **h_addr_list; /* a NULL terminates the list */
                                         };
                                       
                                       #define h_addr h_addr_list[0]; /* first address in list */
                                       
Gegenwärtig enthält das Feld h_addrtype immer den Wert A_INET und analog das Feld h_length immer den Wert 4 (ist gleich der Länge der Internet-Adressse). Bei Internet-Adressen besteht die Matrix der Zeiger h_addr_list[0], h_addr_list [1], ... nicht aus Zeigern auf Zeichen, sondern aus Zeigern auf Strukturen vom Typ in_addr. Die hostent-Struktur ist sehr allgemein gehalten, wobei momentan vieles davon noch nicht verwendet wird.
Das wichtigste Element der hostent-Struktur ist das Feld h_addr_list, das in einem Array die IP-Nummer des Rechners enthält. Das Makro h_addr liefert die Nummer, wie sie in früheren Versionen üblich war. Das Feld h_length liefert die Größe einer IP-Nummer.
Ein Host kann mehr als einen Namen tragen, denn ein universell einsetzbarer Host kann mehr als eine Internet-Schnittstelle besitzen, jede mit einer eindeutigen IP-Adresse. Das folgende Beispiel zeigt die Verwendung der gethostbyname-Funktion.
                                       /* Print the "hostent" information for every host whose name is
                                        * specified on the command line. (nach Stevens)
                                        */
                                       #include <stdio.h>
                                       #include <sys/types.h>
                                       #include <netdb.h>       /* for struct hostent */
                                       #include <sys/socket.h>  /* for AF-INET */
                                       #include <netinet/in.h>  /* for struct in_addr */
                                       #include <arpa/inet.h>   /* for inet_ntoa() */
                                       
                                       void pr_inet(char **listptr, int length);
                                       
                                       int main(int argc, char **argv)
                                         {
                                         char *ptr;
                                         struct hostent *hostptr;
                                       
                                         while (--argc > 0) 
                                           {
                                           ptr = *++argv;
                                           if ((hostptr = gethostbyname(ptr)) == NULL)
                                             {
                                             printf("gethostbyname error for host %s\n",ptr);
                                             continue;
                                             }
                                           printf ("official host name: %s\n", hostptr->h_name);
                                           /* go through the list of aliases */
                                           while ((ptr = *(hostptr->h_aliases)) != NULL)
                                             {
                                             printf("    alias: %s\n", ptr);
                                             hostptr->h_aliases++;
                                             }
                                           printf("    addr type = %d, addr length = %d\n",
                                                  hostptr->h_addrtype, hostptr->h_length);
                                           switch (hostptr->h_addrtype) 
                                             {
                                             case AF_INET: pr_inet(hostptr->h_addr_list, hostptr->h_length);
                                                           break;
                                             default:      printf("unknown address type\n");
                                                           break;
                                             }
                                           }
                                         return 0;
                                         }
                                             
                                       void pr_inet(char **listptr, int length)
                                       /* Go through a list of internet addresses,
                                          printing each one in dotted-decimal notation. */
                                         {
                                         struct in_addr *ptr;
                                         while ( (ptr = (struct in_addr *) *listptr++) != NULL)
                                           printf (" Internet address: %s\n", inet_ntoa(*ptr));
                                         }
                                       
Es gibt auch den Fall, daß ein Server die Internet-Adresse des Clients weiß, aber dessen Namen wissen möchte. Die Funktion gethostbyaddr erledigt in diesem Fall die Konvertierung von Adresse zu Namen:
                                       #include <netdb.h>
                                       ...
                                       
                                       struct hostent *gethostbyaddr (char *Addr, int Len, int Type);
                                       ...
                                       
Der Addr-Parameter ist ein Zeiger auf eine sockaddr_in-Struktur, welche die Internet-Adresse enthält. Len ist die Größe dieser Struktur. Type muß mit AF_INET angegeben werden. Ähnlich wie bei der gethostbyname-Funktion gibt es auch hier viel Allgemeingültiges, von dem jedoch nicht viel verwendet wird.

Die Funktion getservbyname sucht nach einem Dienst - letztendlich nach einem Port:

                                       #include <netdb.h>
                                       ...
                                       struct servent *getservbyname(char *Servicename, char *Protname);
                                       
                                       ...
                                       
Diese Funktion gibt einen Zeiger auf folgende Struktur zurück:
                                       struct servent 
                                         {
                                         char *s_name;      /* official service name */
                                         char **s_aliases;  /* alias list */
                                         int s_port;        /* port number, network byte order */
                                         char *s_proto;     /* protocol to use */
                                         }
                                       
Die Information für diese Funktion wird der Datei /etc/services entnommen. In dieser Datei wird eine Suche nach dem geforderten Service (Servicename) gestartet. Ist auch ein Protokoll angegeben (d. h. Protname != NULL), dann muß der entsprechende Eintrag für dieses Protokoll in der Datei vorliegen. Es gibt einige Internet-Dienste, die entweder von TCP oder UDP unterstützt werden (z. B. der Echodienst), und andere, die nur ein Protokoll unterstützen (FTP erfordert beispielsweise TCP). Das Hauptaugenmerk innerhalb der servent-Struktur liegt auf der Internet-Portnummer. Zu beachten ist, daß diese Struktur Integer-Portnummern handhaben kann, sogar Intenet-Portnummern in 16 bit-Größe. Beispiel:
                                       struct hostent *RechnerID;
                                       struct servent *Service;
                                       ...
                                       
                                         RechnerID = gethostbyname("server");    /* Bestimme den Rechner */
                                         Service = getservbyname("echo","tcp");  /* Bestimme den Port */
                                       ...
                                       
                                       
Das wichtigste Element der servent-Struktur ist das Feld s_port. es enthält die Nummer des Ports, wie sie von der Funktion connect verwendet wird.

Nichtprivilegierte Programme (d. h. Programme ohne Root-Rechte) dürfen keine Server-Sockets auf Ports kleiner 1024 öffnen. So wird ein minimaler Schutz davor gewährleistet, daß irgend welche Programme normaler Anwender Ports kidnappen oder auf Ports eigene Services hochfahren, die die Maschine normalerweise nicht bieten würde. Andererseits ist es aus Sicherheitsaspekten nicht sinnvoll, wenn alle Serverprozesse mit root-Privilegien laufen. Die Lösung des Problems ist einfach: sobald man die Server-Sockets gebunden hat, kann man mit setreuid(2) die Sonderprivilegien gegen "normale" Userprivilegien tauschen. Alternativ kann man Beispielsweise sicherheitsrelevante setuid-Programme in einem chroot(2)-Gefängnis ablaufen lassen, oder das Programm in zwei Prozesse aufteilen, so daß nicht alles mit root-Rechten laufen muß.

Wenn man kurz nachdem ein Programm eine Server-Socket geschlossen hat versucht, einen neuen Socket an denselben Port wie den alten Server-Socket zu binden, erhält man einen "Address already in use"-Fehler. Der Grund dafür ist, daß möglicherweise im Netz noch Pakete herumgeistern, die für den alten Socket bestimmt sind und es deshalb sinnvoll ist, erst einmal zu warten, bis sich das Netz beruhigt hat. Wenn man eine Socket sofort an einen Port binden will, verwendet man die "Reuse"-Option.

DIPLOMARBEITEN UND BÜCHER

Diplomarbeiten zum Runterladen:

Suche im Katalog:
Architektur / Raumplanung
Betriebswirtschaft - Funktional
Erziehungswissenschaften
Geowissenschaften
Geschichtswissenschaften
Informatik
Kulturwissenschaften
Medien- und Kommunikationswissenschaften
Medizin
Psychologie
Physik
Rechtswissenschaft
Soziale Arbeit
Sozialwissenschaften


JOBS
HOME | E-LEARNING | SITEMAP | LOGIN AUTOREN | SUPPORT | FAQ | KONTAKT | IMPRESSUM
Virtual University in: Italiano - Français - English - Español
VirtualUniversity, WEB-SET Interactive GmbH, www.web-set.com, 6301 Zug

Partner:   Seminare7.de - PCopen.de - HTMLopen.de - WEB-SET.com - YesMMS.com - Ausbildung24.ch - Manager24.ch - Job und Karriere