Jeder Anwender zieht verständlicherweise einen Host-/Domain-Namen à la www.camelot.de der zugehörigen numerischen IP-Adresse vor.
Klar, daß nahezu jede Internet-Anwendung das Domain-Name-System zur Umsetzung der symbolischen Namen nutzt.
Wie bereits im ersten Teil angesprochen, ist der DNS im OSI-Modell in der Anwendungsschicht angesiedelt. Jedoch müssen Sie einen DNS-Client nicht wie bei anderen Protokollen dieser Schicht selbst implementieren, sondern können erfreulicherweise auf entsprechende
Funktionen des Socket-Interface zurückgreifen. Für den wohl am häufigsten vorkommenden Fall, die Umsetzung des symbolischen Namen in die numerische IP-Adresse, benötigen Sie gar nur eine Funktion:
hostent *gethostmyname( char *name ) ;
typedef struct
{
int *h_name;
int **h_aliases
int h_addrtype;
int h_length;
char **h_addr_list;
} hostent;
#define h_adr h_addr_list[0]
/* Zum Zugriff auf die erste IP-Adresse */
Beim Aufruf wird der Funktion die Adresse einer Zeichenfolge mit dem Hostnamen übergeben. Zurück liefert sie einen Zeiger auf eine Hostent-Struktur oder NULL, wenn der Hostname nicht aufgelöst werden konnte. Letzteres kann daran liegen, daß der angegebene Host nicht existiert, der DNS-Server die Auskunft verweigert hat oder das DNS mangels Zugang nicht benutzt werden konnte.
War die Anfrage beim DNS-Server erfolgreich, arbeiten Sie mit der Hostent-Struktur weiter: (Siehe Listbox 1)
Die erste Komponente ist ein Zeiger auf eine Zeichenfolge mit dem offiziellen Namen des Hosts. Einer IP-Adresse können durchaus mehrere Namen zugeordnet sein, was zur Folge hat, daß Sie bei einem Aufruf von gethostbynameQ unter Umständen eine andere Bezeichnung als offiziellen Hostnamen zurückbekommen als den ursprünglich übergebenen. Welche alternativen Namen es darüber hinaus noch gibt, erfahren Sie über die zweite Komponente. Sie verweist auf ein Array mit Zeigern, die jeweils auf eine Zeichenfolge mit einem Aliasnamen verweisen. Das Ende des Arrays wird durch einen NULL-Zeiger markiert.
Wollen Sie schlicht die IP-Adresse eines Hosts erhalten, können Sie die beiden ersten Komponenten genau wie die beiden nachfolgenden ignorieren. Letztere sind bei IP-basierender Kommunikation mit PF_INET bzw. 4 initalisiert. Bei anderen Netzwerkarchitekturen, die Definition der Sockel-Schnittstelle ist eigentlich nicht explizit für TCP/IP ausgelegt, stehen hier durchaus andere Werte, die aber bei der Entwicklung von Internet-Anwendungen keine Rolle spielen. Die IP-Adresse(n) finden Sie über die letzte Komponente. Auch hierbei handelt es sich wiederum einen Zeiger auf ein Array aus Zeigern, die auf im Speicher als long abgelegte IP-Adressen verweisen, weshalb zum Zugriff immer eine explizite Typumwandlung durchgeführt werden muss. Das Ende des Arrays erkennen Sie wiederum an einem NULL-Zeiger. Oftmals wird dieses Array nur eine IP-Adresse enthalten. Verfügt der Host aber über mehrere Netzwerkschnittstellen, besteht das Array aus mehreren Elementen.
Nach dem Aufruf von gethostbyname() bekommen Sie die IP-Adresse in der 4-Byte-Darstellung, also in der Form, wie die Socket-Funktionen zum Zugriff auf einen Host benötigen. Zur Darstellung ist dagegen die Vier-Punkte-Form üblich. Die Funktion
char *inet_ntoa( unsigned long ip );
wandelt eine übergebene IP-Adresse in eine Zeichenfolge mit der für uns lesbaren Vier-Punkte-Form um. Mit
unsigned long inet_addr( char *host );
läßt sich der umgekehrte Weg beschreiten.
Da die Ergebnisse von gethostbyname() und inet_ntoa() in einem jeweils statisch allokierten Speicher zur Verfügung gestellt werden, müssen Sie die Informationen, die für die weitere Ausführung des Programms benötigt werden, vor dem jeweilig nächsten Aufruf in einen eigenen Speicherbereich retten (auf die Problematik bei der Verwendung von Threads werde ich im Teil 5 gesondert eingehen).
Das DNS wird erst gar nicht bemüht, falls gethostbyname() als Hostname eine IP-Adresse in der Vier-Punkte-Form übergeben wird. Die Funktion kehrt sofort zurück, und in der Komponente h_addr steht die IP-Adresse in der 4-Byte-Darstellung. Sie haben bei Ihrem Internet-Client demzufolge kein Problem, falls der Anwender die IP-Adresse gleich direkt angibt. Das Programm GETHOST demonstriert eine Möglichkeit, alle Informationen aus dem Ergebnis der hostent-Struktur nach der DNS-Abfrage auszulesen.
In einigen Fällen ist es für eine Internet-Awendung interessant, welche IP-Adresse der lokale Rechner hat. Mit der Funktion
long gethostid( void );
läßt sich diese in der 4-Byte-Darstellung ermitteln.
Die IP-Adresse legt bereits einen bestimmten Rechner im Internet fest. Für die Kommunikation via TCP oder UDP muss aber zusätzlich ein Dienst auf diesen Rechner mit der Port-Nummer gewählt werden. Das Socket-Interface faßt daher die IP-Adresse und Port-Nummer in folgender Struktur zusammen: (Siehe Listbox 2)
Diese Struktur ist nur für die Kommunikation über TCP/IP-basierenden Verbindungen vorgesehen (Sie erinnern sich: das Socket-Interface ist universell ausgelegt). Demzufolge wird in die erste Komponente immer die Konstante AF_INET eingetragen. Die zweite Komponente legt die Port-Nummer und die dritte die IP-Adresse fest. Die letzte Komponente muss für TCP/IP nicht weiter betrachtet werden.
Listbox 2
typedef struct { int sin_family; /* Adreßformat, AF INET / int sin_port; / Port-Nummer / unsigned long sin_addr; / IP-Adresse / char sin_zero[8]; / Full-Bytes */ } sockaddr_in;
Sollten Sie einmal in die Verlegenheit kommen, das Socket-Interface auf einer anderen Plattform zu verwenden, treffen Sie bei der Belegung der sockaddr_in-Struktur möglicherweise auf ein Problem der ganz besonderen Art: Big- und Little-Endian, also die Reihenfolge, in der die Bytes eines WORDs oder LONGs im Speicher abgelegt werden. Da das Internet aus Unix-Rechnern mit Big-Endian-Pro-zessoren hervorging, ist auch die Byte-Reihenfolge, die sogenannte Network Byte Order, im Internet Big-Endian. Im Gegensatz zur TOS-Plattform muss also beispielsweise auf Intel-Rechnern immer dann konvertiert werden, wenn Binärinformationen über Internet-Protokolle ausgetauscht werden, insbesondere bei IP-Adresse und Port-Nummer. Mit insgesamt vier Funktionen lassen sich 16- und 32-Bit-Werte zwischen Big- und Little-Endian wandeln:
unsigned long int ntohl (unsigned long int); /* Network To Host Long */
unsigned short int ntohs (unsigned short int); /* Network To Host Short /
unsigned long int htonl (unsigned long int); / Host To Network Long /
unsigned short int htons (unsigned short int); / Host To Network Short */
Aus naheliegenden Gründen implementiert IConnect diese Funktionen übrigens als Makros, die vom Präprozessor mit den jeweiligen Parameter ersetzt werden.
Wollen Sie Ihre Internet-Clients unabhängig von der Plattform entwickeln, müssen Sie eine IP-Adresse und einen Port folgendermaßen in eine sockaddr_in -Struktur schreiben:
#define IP 1234567
#define PORT 80
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons( PORT);
sin.sin_addr = htonl ( IP );
Beim Auslesen einer "Sockaddr_in"-Stru-k- tur kommen dann entsprechend ntohlQ und ntohs() zum Einsatz. Da ich davon ausgehe, daß die Leser dieser Reihe Internet-Anwendungen für die TOS-Plattform entwickeln, werde ich bei den Programmbeispielen auf diese Problematik nicht weiter eingehen.
Diesmal war der Umgang mit dem DNS und den für die Adressierung vom Socket-Interface vorgegebenen Funktionen und Strukturen der Schwerpunkt. Im nächsten Teil dieser Reihe werde ich Ihnen zeigen, wie Sie Verbindungen zu anderen Rechnern via TCP herstellen und Daten mit TCP bzw. UDP versenden sowie empfangen können. Kurzum: Wir werden die ersten Internet-Anwendungen entwickeln.
Korrektur-Nachtrag zum Heft 1/99:
Leider waren die oberen Kästen der Block-Grafik des ersten Socket-Kurs-Teils nahezu unlesbar gedruckt, was wir Sie zu entschuldigen bitten. Daher hier nochmals der Inhalt der Kästen - oberste Reihe von links nach rechts und an-schließennd von oben nach unten:
Falls Sie Ihre Internet-Software für IConnect j entwickeln, muss Ihr Programm, bevor es irgendeine Socket-Funktion aufruft, sich mit <
int sock init ( void )
bei IConnect anmelden. Eine Rückgabe ungleich Null signalisiert entweder mit SE_NINSTALL, daß SOCKET.PRG von IConnect nicht installiert ist, oder mit SE_NSUPP, daß SOCKET.PRG im Vergleich zur verwendeten Library zu alt ist. In beiden Fällen sollte Ihr Programm jeweils einen entsprechenden Hinweis ausgeben und keine Funktionen des SockeMnterface aufrufen.