A. Hofmann: Socketprogrammierung
Transcription
A. Hofmann: Socketprogrammierung
Socketprogrammierung für IPv4 und IPv6 (Mitfinanziert durch die KTI) Andreas Hofmann, a.hofmann@fhbb.ch April, 2004 Inhaltsverzeichnis 1 Einleitung 4 1.1 Dokumenteninfo 4 1.2 Homepage 4 2 Daten-Strukturen und Bearbeitung 5 2.1 Socket 5 2.2 struct sockaddr 5 2.3 struckt sockaddr_inX (und struct inX_addr) 6 2.3.1 IPv4: struct sockaddr_in (und struct in_addr) 6 2.3.2 IPv6: struct sockaddr_in6 (und struct in6_addr) 7 2.3.3 IPv4/IPv6 portierbar: sockaddr_storage 7 2.4 Konvertierungsfunktionen 8 2.4.1 Byte Order Konvertierungen 8 2.4.2 Namensauflösung 9 2.4.2.1 Namensauflösung für IPv4 9 2.4.2.2 Namensauflösung portierbar für IPv4/IPv6 10 2.4.3 Arbeiten mit IP-Adressen 11 3 Systemaufrufe 11 3.1 Einen Filedescriptor erhalten: socket() 12 3.2 Auf welchem Port soll ich horchen? : bind() 13 3.3 Eine Verbindung herstellen: connect() 14 3.4 Ruf mich an: listen() 14 3.5 Danke für Ihren Anruf auf port 111: accept() 15 3.6 Sprich mit mir: send(), recv() 16 3.7 Verbindungslos (Datagram) sprechen: sendto(), recvfrom() 17 3.8 Alles hat ein Ende: close(), shutdown() 18 3.9 Übersicht und Ablauf Systemaufrufe 20 4 Anhang 21 4.1 Übersicht wichtiger Socketfunktion 21 4.2 Beispielanwendung 22 2 4.2.1 IPv4/IPv6 Server 22 4.2.2 Ipv4/IPv6 Client 24 3 1 Einleitung 1.1 Dokumenteninfo Text in diesem Dokument, welcher Programmcode oder Datenstrukturen beschreibt, ist in der Schriftart Courier dargestellt. Ebenso verwenden auch sonstige wichtige Schlüsselwörter die in Verbindung mit der Socketprogrammierung stehen die selbe Formatierung. Dieses Dokument basiert auf der hervorragenden Vorlage von Brian „Beej“ Hall „Beej's Guide To Network Programming“ wurde etwas verkürzt, dafür um IPv6 Themen ergänzt. Es wird versucht, zusätzlich zur IPv4 Socket Programmierung auf die Socketprogrammierung für IPv6 hinzuweisen und wenn immer möglich, auf so genannte AFindependent Programmierung. Mit AF-independent ist die Adressfamilien-Unabhängigkeit gemeint, also die Unabhängigkeit von AF_INET und AF_INET6 (siehe weiter unten). Es ist der Versuch, Netzwerkprogramme mit einer grösseren Weitsicht zu erstellen, als das damals bei den IPv4 Programmen der Fall war und die nun der Einführung und Ausbreitung von IPv6 derart grosse Steine in den Weg legt. Nicht wenige Personen gehen davon aus, dass IP irgendwann durch ein anderes Protokoll ersetzt werden könnte. Dieses neue Protokoll soll dann nicht wieder mit denselben Problemen zu kämpfen haben, wie dies IPv6 momentan tun muss. Neue Netzwerkprogramme sollten so geschrieben werden, dass sie möglichst keine Abgängigkeit zu der verwendeten Adressfamilien haben. 1.2 Homepage Das Original und die stets aktuelle Version dieses Dokumentes kann unter http://www.fhbb.ch/6power gefunden werden. Das Dokument „Beej's Guide To Network Programming“ ist unter http://www.cest.csuchico.edu/~beej/guide/net gefunden werden. 4 2 Daten-Strukturen und Bearbeitung 2.1 Socket Der Socket ist ein normaler, unter UNIXen gängiger Filedescriptor. Die Socket API tauchte erstmals 1982 bei UNIX 4.1 (BSD Unix) der Berkeley University auf. Diese API sollte eine analoge Funktionalität zu den normalen Datei I/O Routinen unter UNIX bieten. Es sollte so möglichst mit den selben Funktionen wie sie bereits zum Schreiben in und Lesen aus Dateien bekannt waren, auch über ein Netzwerk Informationen ausgetauscht werden können. Ein Socket definiert die Kommunikationsbeziehung zwischen zwei Systemen Definition Socket: Ein Socket bezeichnet ein Kommunikationsendpunkt innerhalb einer Kommunikationsbeziehung. Eine Kommunikiationsbeziehung zwischen zwei Kommunikationspartnern besteht also immer aus einem Socketpaar! Der Datentyp eines solchen Sockets ist ein normaler Integer Wert, wie dies bei allen Filedeskriptoren der Fall ist. 2.2 struct sockaddr Diese Struktur enthält die Adressinformationen für viele Sockettypen. Es ist die generische Struktur, die bei allen Socketfunktionen benutzt wird. struct sockaddr { unsigned short sa_family; // AF_INET, AF_INET6 char sa_data[14]; // 14 Bytes Daten }; sa_data enthält die Zieladresse und den Zielport für einen Socket! sockaddr wird benötigt um einen Socket zu erstellen. Da es aber ziemlich unhandlich wäre, die benötigten Adressinformationen in das 14 Byte gross sa_data Feld abzusetzen, wurde eine weitere Datenstruktur erstellt, welche eben diese Arbeit erleichtern soll: struct sockaddr_in 5 2.3 struckt sockaddr_inX (und struct inX_addr) Für die Umstellung auf IPv6 hat sich in dieser Struktur etwas geändert. Es wurde ein IPv6 Pendant dieser Struktur erstellt. Aber noch viel wichtiger, es gibt ein portierbare Version dieses Structs, die unbedingt verwendet werden sollte, da diese Version sowohl mit IPv4 als auch mit IPv6 umgehen kann. 2.3.1 IPv4: struct sockaddr_in (und struct in_addr) Diese Struktur ist sozusagen ein Parallelstruct zu sockaddr, welches einen einfacheren Umgang mit den Adressinformationen eines Sockets bieten soll. Das „_sin“ im Namen steht für Internet. typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { short int sin_family; // Adressfamilie AF_INET, AF_INET6 unsigned short int sin_port; //Portnummer in Network Byte Order struct in_addr sin_addr; //Inernetadresse in Network Byte Order unsigned char sin_zero[8]; //Padding um die Grösse von sockaddr zu erreichen }; Diese Struktur macht es einfach, auf die Elemente einer Socketadresse zuzugreifen. Beachten Sie, dass sin_zero den Zweck hat, die struct sockaddr_in mit zusätzlichen 8 Bytes auf die selbe Grösse wie sockaddr zu bringen! sin_zero sollte mit memset() mit lauter Nullen aufgefüllt werden! Wichtig: Ein Zeiger auf struct sockaddr_in kann in einen struct sockaddr „typumgewandelt“ werden und umgekehrt! Obwohl also die Funktion socket() (siehe weiter unten) einen struct *sockaddr erwartet, kann mit der viel einfacher zu handhabenden struct sockaddr_in gearbeitet werden. Diese struct sockaddr_in wird im letzten Moment nach struct *sockaddr umgewandelt! Es gilt weiter zu beachten, dass sin_port und sin_addr in der „Network Byte Order“ stehen müssen! 6 2.3.2 IPv6: struct sockaddr_in6 (und struct in6_addr) Die neue Struktur für IPv6 sieht fogendermassen aus: struct in6_addr { // Achtung: union ermöglicht hier auf unterschiedliche // Weise auf denselben Speicherplatz zuzugreifen! union { uint8_t u6_addr8[16]; // 8 * 16 = 128 ! uint16_t u6_addr16[8]; // 16 * 8 = 128 ! uint32_t u6_addr32[4]; // 32 * 4 = 128 ! } in6_u; // mit in6_u kann allgemein zugegriffen werden // defines um noch einfacher auf das union zuzugreifen #define s6_adrr in6_u.u6_addr8 #define s6_addr16 in6_u.u6_addr16 #define s6_addr32 in6_u.u6_addr32 }; struct sockaddr_in6 { sa_family sin6_family; //AF_INET6 in_port sin6_port; //Transport Layer Port struct in6_addr sin6_addr; //IPv6 Adresse uint32_t sin6_scope_id; //IPv6 Scope-ID }; 2.3.3 IPv4/IPv6 portierbar: sockaddr_storage Diese Struktur verbirgt die die spezifisch verwendete IPv4 oder IPv6 Struktur! #if ULONG_MAX > 0xffffffff # define __ss_aligntype __uint64_t #else # define __ss_aligntype __uint32 #endif #define _SS_SIZE 128 #define _SS_PADSIZE (_SS_SIZE – (2 * sizeof(__ss_aligntype))) struct sockaddr_storage{ sa_family ss_family; //Adress Familie 7 __ss_aligntype __ss_align; //Alignment erzwingen char __ss_padding[_SS_PADSIZE]; }; Diese Struktur soll also an Stelle von sockaddr_in und sockaddr_in6 gewählt werden. Eine Anwendung könnte dann so aussehen: struct sockaddr_storage addr; socklen_t addrlen; /** Adress-Struktur abfüllen mit entweder IPv4-oder Ipv6-Adresse. Ausserdem muss addrelen mit der richtigen Grösse (sizeof(addr) ) gefüllt werden, bevor die socket() Funktion aufgerufen wird. ... */ bind(sockfd, (struct sockaddr *)&addr, addrlen); 2.4 2.4.1 Konvertierungsfunktionen Byte Order Konvertierungen Verschiedene Rechnerarchitekturen speichern „mehrbytige“ Zahlen intern in unterschiedlicher Byteanordnung ab. Damit das bei der Übertragung von Daten zwischen zwei unterschiedlichen Rechnerarchitekturen über ein Netzwerk nicht zu Problemen führt, einigte man sich für eine Netzwerk-Byteanordnung, die „Network Byte Order“. Die Network Byte Order benutzt die „Big-Endian Byte Order“ (auch „Least Significant Byte Order“ genannt). Einige Rechnerarchitekturen benutzen jedoch die „Little-Endian Byte Order“ (auch „Most Significant Byte Order“ genannt). Für die Übertragung von Daten über ein Netzwerk müssen die Daten also stets in die Network Byte Order gebracht werden. Hierzu stehen gewisse Funktionen zur Verfügung. Man soll diese Funktionen immer verwenden, auch dann, wenn man glaubt zu wissen, dass der eigene Rechner intern ebenfalls die „Big-Endian Byte Order“ benutzt. Der Programmcode wird so besser auf andere Rechnersysteme portierbar! Die Namen der Funktionen, welche diese Byteorder Konvertierungen vornehmen, setzen sich immer aus „h“ für Host, „to“ für zu, „n“ für Network, „s“ für short und „l“ für long 8 zusammen. Folgende Funktionen stehen zur Verfügung: – htons() : „Host To Network Short“ – htonl() : „Host To Network Long“ – ntohs() : „Network To Host Short“ – ntohl() : „Network To Host Long“ sin_addr und sin_port müssen z.B. Immer in die Network Byte Order gebracht werden. Hingegen muss dies sin_family nicht, da sin_family gar nicht über das Netzwerk übertragen wird, sondern lediglich dem lokalen Kernel dazu dient um festzustellen, welcher Typ Adresse verwendet wird! 2.4.2 Namensauflösung Anwendungen sollten mit Hostnamen anstelle mit Hostadressen arbeiten. Hostnamen sind einfacher zu merken und bleiben länger unverändert, während sich die Adresse eines Hosts öfters ändern kann. Wenn Hostnamen verwendet werden, so müssen die Namen in IPAdressen übersetzt werden, bevor mit den Socketfunktionen gearbeitet werden kann. Hierfür stehen spezielle Funktionen zur Verfügung. 2.4.2.1 Namensauflösung für IPv4 #include <netdb.h> struct hostent *gethostbyname(const char *hostname); Der Parameter dürfte selbsterklärend sein. Als Rückgabewert erhält man einen Zeiger auf struct hostent, welcher alle Informationen zum erfrgaten Hostnamen besitzt: struct hostent{ char *h_name; //Offizieller Name des Rechners char **h_aliases; //Nullterm. Vektor mit Alternativnamen int h_addretype; // AF_INET, AF_INET6 int h_length; // Länge der Adresse in Bytes char **h_addr_list; // Nullterm. Verktor mit den // Netzwerkadressen des Host in Network 9 // Byte Order }; #define h_addr h_addr_list[0]; 2.4.2.2 Namensauflösung portierbar für IPv4/IPv6 Es gibt zwei neue Funktionen um die Adress- und Namensauflösung vorzunehmen, welche auch mit IPv4 Adressen umgehen kann. Diese sollten immer an Stelle der gethostbyname() und gethostbyaddr() verwendet werden: • getaddrinfo() • getnameinfo() Die Funktion getaddrinfo()gibt eine verlinkte Liste von struct addrinfo zurück, die Informationen zum befragten Host liefert. struct addrinfo { int ai_flags; //AI_PASSIVE, AI_CANONNAME int ai_family; //AF_INET, AF_INET6,AF_UNSPEC int ai_socktype; //SOCK_STREAM, SOCK_DGRAM int ai_protocol; //IPPROTO_IP, IPPROTO_IPV6 size_t ai_addrlen; //Länge von ai_addr struct sockaddr ai_addr; //Generische Sockadr.Strukt. char ai_canonname; // struct addrinfo ai_next; //Verweis auf nächste //addrinfo zum selben Host!! }; int getaddrinfo( const char *node, //Name des Hosts const char *service, //Name Dienst (Dienstanfrage) const struct addrinfo *hints, //Weitere Bed. angeben struct addrinfo **result //Hier das Res. In Liste ); 10 2.4.3 Arbeiten mit IP-Adressen Angenomen, Sie haben ein struct sockaddr_in und wollen dort die IP-Adresse 10.1.93.110 unterbringen, so muss diese IP-Adresse in ein unsigned long überführt werden! Hierzu gibt es die Funktion: • inet_addr() Sie konvertiert eine IP-Adresse in der Punktschreibweise in ein unsigned long. Diese Konvertierung geschieht übrigens gleich in die Network Byte Order! Es existiert auch noch die Funktion inet_aton() (aton = „Ascii To Network“) welche eigentlich vorzuziehen wäre, jedoch ist diese Funktion nicht auf alle Platformen implementiert, so dass oft die Funktion inet_addr() verwendet wird. Will man das Umgekehrte erreichen, also aus einer IP-Adresse in einem struct sockaddr_in eine IP-Adresse von unsigned long (Network Byte Order) nach der gepunkteten Schreibweise überführen, so verwendet man folgende Funktion: • inet_ntoa() : „Network To Ascii“ Beachten Sie: Für die Behandlung von DNS-Namen und die Überführung in IP-Adressen, gibt es eigene Funktionen. 3 Systemaufrufe Die nächsten Funktionen beschreiben das Vorgehen um auf ein Netzwerk zuzugreifen. Dabei ist die Reihenfolge, in welcher die Funktionen erklärt werden, gerade diejenige, die Sie in ihrem Programm auch verwenden müssen. Wenn Sie eine dieser Funktionen aufrufen, so „übernimmt“ in der Regel der Kernel das weitere Vorgehen und „erledigt die Arbeit“. Die Socket-API hat sich bei der Erweiterung auf IPv6 eigentlich nicht geändert, da diese generische, von der verwendeten Protokollfamilie unabhängige, Adressstrukturen verwendet. Trotzdem muss in einer Anwendungen bei den Socketfunktionen eine Änderung vorgenommen werden, da als Argument of die Adressfamilie fest mitgegeben wird 11 (AF_INET). Diese muss angepasst werden und je nach zu verwendeter Adressfamilie auf AF_INET oder AF_INET6 gesetzt werden! Es sind folgende 3 Änderungstypen identifiziert worden, wo sich eine Umstellung auf IPv6 oder portierbaren Code bemerkbar machen: • Aufrufen von Socketfunktionen mit versionsabhängigen Parametern: • • int socket(int domain, int type, int protocol); Übergabe von Socket Adressstrukturen von der Anwendung zum Kernel • int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); • int connect(int sockfd, const struct sockaddr *server_addr, socklen_t addrlen); • int sendto(int sockfd, const void *msg, size_t msglen, int flags, struct sockaddr *from_addr, socklen_t *fromlen); • Übergabe von Socket Adressstrukturen vom Kernel an die Anwendung • int accept(int sockfd, struct sockaddr *from_addr, socklen_t *addrlen); • int recvfrom(int sockfd, void *buf, size_t buflen, int flags, struct sockaddr *from_addr, socklen_t *fromlen); • int getpeername(int sockfd, struct sockaddr *name, socklen_t *namelen); • int getsockname(int sockfd, struct sockaddr *name, socklen_t *namelen); 3.1 Einen Filedescriptor erhalten: socket() #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); Parameter: • domain: AF_INET, AF_INET6 hier kommt derselbe Wert wie in sockaddr_in • type: Welcher Typ Socket soll verwendet werden SOCK_STREAM für TCP, SOCK_DGRAM für UDP 12 • protocol: Mit dem Wert 0 wählt socket() aufgrund von type selbst das richtige Protokoll. Es gibt jedoch auch eine Funktion, die das eleganter macht: getprotobyname(). Man kann aber üblicherweise einfach eine 0 einsetzen. Bei einem Fehler gibt socket() den Wert -1 zurück und setzt errno auf den Fehlercode, der mit perror(errno) ausgegeben werden kann. 3.2 Auf welchem Port soll ich horchen? : bind() #include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, struct sockaddr *my_addr, int addrlen); Parameter: • sockfd: Der Socketfiledescriptor den wir von socket() erhalten haben • my_addr: Zeiger auf struct sockaddr, welcher die Informationen zur Adresse und zum Port des lokalen Rechners beinhaltet. • addrlen: Wird auf sizeof(struct sockaddr)gesetzt. Die Funktion bind() wird aufgerufen, wenn man eine Serveranwendung schreiben will, die Verbindungsanfragen entgegen nimmt! Um die eigene IP-Adresse herauszufinden gibt es sogenannte Makros: • my_addr.sin_addr.s_addr = htonl(INADDR_ANY); INADDR_ANY in diesem Beispiel ist ein Makro, welches die eigene IP-Adresse einsetzt. Die Verwendung von htonl() ist in diesem Beispiel nicht zwingend nötig, da INADDR_ANY lauter Nullen enthält. Trotzdem ist es vernünftig, die Funktion prinzipiell zu verwenden, wenn Daten in die Network Byte Order. Um den Port zu bestimmen, auf welchem die Anwendung Verbindungen entgegen nehmen soll, wird folgendes gemacht: • my_addr.sin_port = htons(110); 13 3.3 Eine Verbindung herstellen: connect() #include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, struct sockaddr *server_addr, int addrlen); Parameter: • sockfd: Der Socketfiledescriptor den wir von socket() erhalten haben • my_addr: Zeiger auf struct sockaddr, welcher die Informationen zur Adresse und zum Port des Zielrechners beinhaltet. • addrlen: Wird auf sizeof(struct sockaddr)gesetzt. Bei einem Fehler gibt connect() den Wert von -1 zurück und setzt errno mit dem Wert des Fehlercodes. connect() wählt sich selbst einen freien Port als Ausgansport auf dem lokalen Rechner. 3.4 Ruf mich an: listen() #include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog); Parameter: • sockfd: Der Socketfiledescriptor den wir von socket() erhalten haben • my_addr: Anzahl Verbindungen in der Eingangswarteschlange. Solange kein accept() (siehe weiter unten) gemacht wird, kommt eine eingehende Verbindung in eine Warteschlange. backlog gibt an, wieviele wartende Verbindungen in dieser Schlange sein dürfen. Üblich ist ein Wert von ~20. listen() gibt -1 beim Auftreten eines Fehlers zurück und setzt errno mit dem Fehlercode! 14 Mit connect() konnten wir eine Verbindung auf einen Rechner herstellen, welcher auf Verbindungsanfragen wartet. Um nun selbst eine Anwendung zu schreiben, die ebenfalls auf Verbindungen wartet, benötigen wir die Funktion listen(). 3.5 Danke für Ihren Anruf auf port 111: accept() #include <sys/socket.h> int accept(int sockfd, void *addr, int *addrlen); Parameter: • sockfd: Der Socketfiledescriptor den wir von socket() erhalten haben • addr: Zeiger auf einen lokalen struct sockaddr_in. Hier werden beim Aufruf von der Funktion die Adressinformationen der aufrufenden Station hinein geschrieben. • addrlen: Zeiger auf die Grösse von addr. Wird auf sizeof(struct sockaddr_in) gesetzt und kann von accept() verändert werden (deswegen der Zeiger). accept() gibt -1 beim Auftreten eines Fehlers zurück und setzt errno mit dem Fehlercode! Wichtig: Wenn alles klappt, so ist der Rückgabewert ein neuer Socketdescriptor, der die eingegangene Verbindung identifiziert! Dieser neue Socketdescriptor wird für weitere Aufrufe von Funktionen wie send() und recv() verwendet! Beispiel (vereinfacht): int sockfd; int new_sockfd; sockfd = socket(); bind(sockfd, ...); listen(sockfd, ...); new_sockfd = accept(sockfd, ...); 15 3.6 Sprich mit mir: send(), recv() #include <sys/socket.h> int send(int sockfd, const void *msg, int len, int flags); Parameter: • sockfd: Der Socketfiledescriptor den wir von socket() oder accept() erhalten haben • msg: Zeiger auf die zu sendenden Daten • len: Länge der zu sendenden Daten in Bytes • flags: Können einfach auf 0 gesetzt werden send() gibt bei einem Fehler den Wert von -1 zurück und setzt errno mit dem Wert des Fehlercodes. Wenn send() erfolgreich war, so ist der Rückgabewert die Anzahl gesendeter Bytes! Achtung: Ist die Anzahl versendeter Bytes kleiner als len, so muss man selbst dafür besorgt sein, dass der Rest versendet wird! receive() funktioniert ähnlich: #include <sys/socket.h> int recv(int sockfd, void *buf, int len, unsigned int flags); Parameter: • sockfd: Der Socketfiledescriptor auf dem gelesen werden soll und den wir via connect() erhalten haben • buf: Puffer in welchen die Informationen geschrieben werden sollen • len: Die maximale Puffergrösse • flags: Können wieder einfach auf 0 gesetzt werden 16 recv() gibt -1 bei einem Fehler zurück und setzt errno mit dem Fehlercode. Bei Erfolg gibt die Funktion die Anzahl Bytes an, die in den Puffer geschrieben wurden. Wenn 0 zurückgegeben wird, so bedeutet das, dass die Gegenstelle die Verbindung abgebrochen hat! 3.7 Verbindungslos (Datagram) sprechen: sendto(), recvfrom() #include <sys/socket.h> int sendto(int sockfd, const void *msg, int len, unsigned int flags, struct sockaddr *from, int *fromlen ); Parameter: Es sind viele Parameter identisch zu send(). Nur wird hier nicht erst eine Verbindung mit connect() aufgebaut und deswegen muss in sendto() selbst die Adresse und der Port des Zielrechners stehen. Die zu sendto() unterschiedlichen Parameter sind: • to: Zeiger auf struct sockaddr welcher wohl eine struct sockaddr_in sein wird und im letzten Moment getypecasted wird. Er enthält die Zieladresse und den Zielport. • tolen: Wird einfach auf sizeof(struckt sockaddr) gesetzt Bei einem Fehler wird wieder -1 zurückgegeben und errno mit dem Fehlercode versehen. Bei Erfolg wird wieder die Anzahl gesendeter Bytes zurückgegeben. Auch hier gilt wieder, dass wenn die Anzahl gesendeter Bytes kleiner ist wie len , man selbst dafür besorgt sein muss, dass die restlichen Bytes gesendet werden. 17 #include <sys/socket.h> int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen ); Parameter: Sind wieder ähnlich wie bei recv() ausser einiger weniger zusätzlicher Paremeter: • from: Zeiger auf einen lokalen struct sockaddr der mit der IPAdresse und dem Port der sendenden Maschine aufgefüllt wird. • fromlen: Zeiger auf einen lokalen int der mit der Funktion sizeof(struct sockaddr) initialisiert werden soll und von der Funktion recvfrom() verändert werden kann. Es werden die Anzahl empfangener Bytes zurückgegeben oder -1, wenn ein Fehler aufgetreten ist. Bei einem Fehler wird wieder errno mit dem Fehlercode gesetzt. 3.8 Alles hat ein Ende: close(), shutdown() int close(int filedescriptor); Parameter: • filedescriptor: socketfd den wir schliessen möchten Bei einem Fehler wird wieder -1 zurückgegeben und errno wieder mit dem Fehlercode versehen. Der Socket filedescriptor den man angibt wird so geschlossen. Das verhindert weitere read()s und write()s auf diesem Socket. Jemand, der weiter auf diesem Socket liest oder schreibt erhält einen Fehler gemeldet. 18 Wenn man einen Socket mit etwas mehr Kontrolle schliessen will, so verwendet man: int shutdown(int filedescriptor, int how); Parameter: • filedescriptor: socketfd dessen Zustand wir ändern wollen • how: 0 : weiteres Empfangen verboten 1: weiteres Senden verboten 2: weiteres Senden und Empfangen verboten Achtung: shutdown() schliesst den Socket nicht wirklich. Es wird nur sein Zustand und somit seine Nutzbarkeit verändert. Am Ende muss ein mit shutdown() veränderter Socket trotzdem mit close() richtig geschlossen werden! 19 3.9 Übersicht und Ablauf Systemaufrufe Abbildung 1: Ablauf Systemaufrufe mit Sockettype SOCK_STREAM (TCP) 20 4 Anhang 4.1 Übersicht wichtiger Socketfunktion Allgemeine: • int socket(int domain, int type, int protocol); • int listen(int sockfd, int backlog); • ssize_t write(int sockfd, const void *buf, size_t count); • int send(int sockfd, const void *msg, size_t len, int flags); • int sendmsg(int sockfd, const struct msghdr *msg, int flags); • ssize_t read(int sockfd, void *buf, size_t count); • int recv(int sockfd, void *buf, size_t len, int flags); • int recvmsg(int sockfd, struct msghdr *mdg, int flags); • int close(int sockfd); • int shutdown(int sockfd, int how); Funktionen in welchen Socket Adressstrukturen von der Anwendung an den Kernel gereicht werden: • int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); • int connect(int sockfd, const struct sockaddr *server_addr, socklen_t addrlen); • int sendto(int sockfd, const void *msg, size_t msglen, int flags, struct sockaddr *from_addr, socklen_t *fromlen); Funktionen in welchen Socket Adressstrukturen vom Kernel zur Anwendung gereicht werden: • int accept(int sockfd, struct sockaddr *from_addr, socklen_t *addrlen); • int recvfrom(int sockfd, void *buf, size_t buflen, int flags, struct sockaddr *from_addr, socklen_t *fromlen); • int getpeername(int sockfd, struct sockaddr *name, socklen_t *namelen); • int getsockname(int sockfd, struct sockaddr *name, socklen_t *namelen) 21 4.2 Beispielanwendung 4.2.1 IPv4/IPv6 Server #include <ctype.h> #include <errno.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/resource.h> /* getrusage() */ #include <sys/socket.h> #include <stdlib.h> /* malloc(3) */ #include <netdb.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> int main(int argc, char **argv){ int sockfd, connfd, error; socklen_t addrlen; struct sockaddr_storage clientaddr; char clienthost[100],clientservice[6]; struct addrinfo hints, *res, *ressave; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; sockfd = -1; error = getaddrinfo(NULL, "20100", &hints, &res); if(error){ perror(gai_strerror(error)); exit(-4); } else{ ressave = res; while(res){ sockfd=socket(res->ai_family, res->ai_socktype, res->ai_protocol); 22 if(!(sockfd < 0)){ if(bind(sockfd, res->ai_addr, res->ai_addrlen) == 0){ break; }; close(sockfd);//Wenn mit dieser res es nicht klappte, muessen wir auch //den socket wieder schliessen!!!! sockfd = -1; } res = res->ai_next; }; if(sockfd < 0){ freeaddrinfo(ressave); fprintf(stderr, "socket error:: could not open socket\n"); return -1; }; /**Wenn wir jetzt noch hier sind, dann ist alles ok */ listen(sockfd, 20); freeaddrinfo(ressave); /**Jetzt koennen wir (endlos) auf Verbindungen warten */ for( ; ; ){ addrlen = sizeof(clientaddr); connfd = accept(sockfd, (struct sockaddr *) &clientaddr, &addrlen); if(connfd < 0){ continue; }; /**Wir koennen nun die Infos des Clients auswerten: */ getnameinfo((struct sockaddr *) &clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST); if(!(connfd < 0)){ printf("Anfrage erhalten: Host=[%s] remote Port=[%s]\n",clienthost, clientservice); char msg[200]; sprintf(msg, "Hallo [%s], habe die Anfrage von deinem Port [%s] erhalten\n", clienthost, clientservice); error = send(connfd, &msg, sizeof(msg), 0); }; }; }; return 1; }; 23 4.2.2 Ipv4/IPv6 Client #include <ctype.h> #include <errno.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> /* malloc(3) */ #include <netdb.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> int main(int argc, char **argv){ int sockfd, connfd, error; int addrlen; char serverhost[1000], *hostname; struct addrinfo hints, *res, *ressave; hostname = argv[1]; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(hostname, "20100", &hints, &res); if(error < 0){ fprintf(stderr, "getaddrinfo error:: [%s]\n", gai_strerror(error)); exit(-4); } ressave = res; sockfd = -1; 24 while(res){ sockfd = socket(res->ai_family, res->ai_socktype, 0); if(!(sockfd < 0)) { if(connect(sockfd, res->ai_addr, res->ai_addrlen)==0){ printf("successfully connected::\n"); break; }; fprintf(stderr, "\nconnect error:: [%s]\n", gai_strerror(errno)); close(sockfd); //Wenn es mit aktuellem res es nicht klappte, //muessen wir auch den socket wieder schliessen!!!! sockfd = -1; }; res = res->ai_next; }; freeaddrinfo(ressave); if(sockfd < 0){ fprintf(stderr, "socket error:: could not open socket\n"); return -1; }; /**Wenn wir jetzt noch hier sind, dann ist alles ok */ char buf[200]; error = recv(sockfd, &buf, sizeof(buf), 0); if(error == -1){ perror(gai_strerror(errno)); exit(-4); } printf("Meldung erhalten: %s\n", buf); return 1; }; 25