A. Hofmann: Socketprogrammierung

Transcription

A. Hofmann: Socketprogrammierung
Socketprogrammierung
für IPv4 und IPv6
(Mitfinanziert durch die KTI)
Andreas Hofmann, [email protected]
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