TCPIPProgrammierung 6 Grundlagen der Socketprogrammierung Hochschule Fulda FB
TCP/IP-Programmierung 6 Grundlagen der Socketprogrammierung Hochschule Fulda – FB AI Wintersemester 2017/18 http: //tp. rz. hs-fulda. de Peter Klingebiel, HS Fulda, FB AI
Kommunikation Client-Server 1 Server socket() Server fordert Socket an Client bind() Server legt Port fest listen() Server bereit accept() Server wartet Client stellt Verbindung her connect() read() Server liest Client schreibt write() Server schreibt Client liest read() close() Server beendet Verbindung Client beendet Verbindung close() Client fordert Socket an TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI socket() 2
Systemcalls 1 • Socket-Systemcalls Phase Server Client Socket erzeugen socket() Adresse binden bind() Warteschlange festlegen listen() Verbindung erwarten accept() Verbindung aufbauen connect() Daten senden write(), sendto(), sendmsg() Daten empfangen read(), recvfrom(), recvmsg() Verbindung schließen shutdown() Socket abbauen close() • es gibt noch eine Fülle weiterer Systemcalls / Funktionen TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 3
socket() 1 • Socket erzeugen (Server und Client) #include <sys/socket. h> int socket(int family, int type, int proto); • liefert einen Filedeskriptor, -1 bei Fehler • Fehlerursache in errno perror() • family: gibt die Adressfamilie an, wichtig sind AF_INET // Internet-Socket IPv 4 AF_UNIX // Unix-/Local-Socket AF_INET 6 // Internet-Socket IPv 6 • type: benennt den Typ des Socket, wichtig sind SOCK_STREAM // Stream-Socket TCP SOCK_DGRAM // Datagramm-Socket UDP SOCK_RAW // roher Socket IP/ICMP • proto: ggfs. Protokoll, falls mehrere vorhanden, i. d. R. 0 TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 4
bind() 1 • Socket an Adresse binden (Server) #include <sys/socket. h> int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen); • bindet einen Socket an Adresse / Adress-Datenstruktur • return 0, wenn ok, -1 bei Fehler • sockfd: Socket-Filedescriptor • addr: "generische" Adressdatenstruktur struct sockaddr { sa_family_t sa_family; // Adressfamilie char sa_data[14]; // Platzhalter } • addrlen: Größe der Datenstruktur in Bytes TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 5
bind() 2 • die "generische" Adressdatenstruktur struct sockaddr muss bei Aufruf von bind()mit einer aktuellen Datenstruktur übergeben werden, die der Adressfamilie entspricht • Beispiel: AF_INET (Internet IPv 4) #include <sys/socket. h> #include <netinet/in. h> // Adresse für Internet IPv 4 struct sockaddr_in { sa_family_t sin_family; // uint 16_t sin_port; // struct in_addr sin_addr; // } // IP-Adress-Datenstruktur struct in_addr { uint 32_t s_addr; // } TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI Adressfamilie Portnummer IP-Adresse IPv 4 -Adresse 6
bind() 3 • Beispiel: AF_UNIX (lokale Unix-Domain) #include <sys/socket. h> #include <sys/un. h> // Adresse für lokale Unix-Domain struct sockaddr_un { sa_family_t sun_family; // Adressfamilie char sun_path[]; // Pfadname } • Parametrisieren mit konkretem Adresstyp (hier AF_INET) // Serverdatenstruktur initialisieren memset(&servaddr, 0, sizeof(servaddr); servaddr. sin_family = AF_INET; servaddr. sin_addr. s_addr = INADDR_ANY; servaddr. sin_port = htons(PORTNUMBER); bind(servfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 7
bind() 4 • Parametrisieren mit konkretem Adresstyp (hier AF_UNIX) // Serverdatenstruktur initialisieren memset(&servaddr, 0, sizeof(servaddr); servaddr. sun_family = AF_UNIX; strcpy(servaddr. sun_path, SOCKET_PATH); bind(servfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); • das Typecast (struct sockaddr *) &servaddr ist notwendig, um dem Compiler mitzuteilen, dass der in der Schnittstelle definierte Datentyp struct sockaddr * hier durch den konkreten, d. h. der Adressfamilie AF_UNIX entsprechenden Datentyp struct sockaddr_in * überladen wird • damit sind problemlos Erweiterungen der Socket-API möglich, ohne die Schnittstellen der API anpassen zu müssen • z. B. bei Einführung von IPv 6 (AF_INET 6) TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 8
listen() 1 • Socket als Empfangssocket einstellen und Länge der Wartschlange festlegen (Server) #include <sys/socket. h> int listen(int sockfd, int backlog); • stellt Socket auf Empfang für accept() ein und legt die maximale Länge der Empfangswarteschlange fest • ein Verbindungswunsch vom Client wird abgewiesen, wenn die Warteschlange wartender Clients voll ist • return 0, wenn ok, -1 bei Fehler • sockfd: Socket-Filedescriptor • backlog: Länge der Warteschlange TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 9
accept() 1 • an Socket auf Verbindung von Client warten und ersten Client aus der Warteschlange bedienen (Server) #include <sys/socket. h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); • • • nimmt Verbindungswunsch von Client connect() an return Client-Filedeskriptor, wenn ok, -1 bei Fehler sockfd: Socket-Filedescriptor des Server addr: liefert Adressdatenstruktur des Client addrlen: liefert Größe der Adressdatenstruktur in Bytes Beispiel: length = sizeof(clientaddr); clientfd = accept(servfd, (struct sockaddr *) &clientaddr, &length); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 10
connect() 1 • Verbindung von Client zu Server aufbauen (Client) #include <sys/socket. h> int connect(int sockfd, struct sockaddr *addr, socklen_t addrlen); • baut Verbindung vom Client zum in der Adressdatenstruktur definierten Server auf • return 0, wenn ok, -1 bei Fehler • sockfd: Socket-Filedescriptor des Client • addr: Adressdatenstruktur des Server • addrlen: Größe der Adressdatenstruktur in Bytes • Beispiel: accept(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 11
close() und shutdown() 1 • Socket ganz oder teilweise schliessen (Server und Client) int close(int sockfd); int shutdown(int sockfd, int how); • close()baut Verbindung ab und schliesst den Socket. Filedeskriptor, entspricht shutdown(sockfd, 2) • shutdown()schliesst eine Richtung der bidirektionalen Socketverbindung oder schliesst die Verbindung • return 0, wenn ok, -1 bei Fehler • sockfd: Socket-Filedescriptor • how: legt fest, wie die Verbindung abgebaut wird: – SHUT_RD: kein Datenempfang (Lesen) mehr – SHUT_WR: kein Datenversand (Schreiben) mehr – SHUT_RW: kein Empfang und Versand mehr (wie close()) TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 12
read() und recv() 1 • Leseoperationen (Server und Client) ssize_t read(int sockfd, char *buffer, int len); ssize_t recv(int sockfd, char *buffer, int len, int flags); • • sockfd: Socket-Filedeskriptor buffer: Datenpuffer zum Lesen / Empfangen len: Größe des Datenpuffers flags: wie sollen Daten gelesen werden – MSG_OOB: empfange OUT-of-Band-Daten – MSG_PEEK: Peek auf eingehende Daten • ist bei recv() flags auf 0 gesetzt, verhält sich recv() wie read() • return Anzahl der gelesenen Bytes, 0 bei EOF, -1 bei Fehler TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 13
write() und send() 1 • Schreiboperationen (Server und Client) ssize_t write(int sockfd, char *buffer, int len); ssize_t send(int sockfd, char *buffer, int len, int flags); • • sockfd: Socket-Filedeskriptor buffer: Datenpuffer mit Schreib-/Sendedaten len: Anzahl der Daten zu schreiben/senden flags: wie sollen Daten geschrieben werden – MSG_OOB: sende OUT-of-Band-Daten – MSG_DONTROUTE: Leite Routing um • ist bei send() flags auf 0 gesetzt, verhält sich send() wie write() • return Anzahl der gesendeten Bytes, 0 bei EOF, -1 bei Fehler TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 14
Zahlenformate und -konversion 1 • Internetadressen und Portnummern sind Zahlen, die binär über das Netz verschickt werden • sie müssen daher unabhängig von der internen Darstellung im Rechner sein • Bsp: Intel nutzt die Reihenfolge Little-Endian, d. h. im Speicher steht das niederwertige Byte zuerst (Host-Order), während RISC-Rechner Big-Endian verwenden (Net-Order) und das höchstwertige Byte zuerst im Speicher abbilden Order uint 16_t uint 32_t big-endian (Net-Order) B 1 B 2 B 3 B 4 little-endian (Host-Order) B 2 B 1 B 4 B 3 B 2 B 1 • im Netz wird die Net-Order (big-endian) verwendet • ggfs. ist Konversion in das Netzwerkformat notwendig TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 15
Zahlenformate und -konversion 2 • zur Konversion Hostorder Net-Order stehen einige Funktionen zur Verfügung • uint 32_t htonl(uint 32_t hlong); wandelt 32 -Bit (l) von Host-Order Net-Order (h n) • uint 32_t ntohl(uint 32_t nlong); wandelt 32 -Bit (l) von Net-Order Host-Order (n h) • uint 16_t htons(uint 16_t hshort); wandelt 16 -Bit (s) von Host-Order Net-Order (h n) • uint 16_t ntohs(uint 16_t nshort); wandelt 16 -Bit (s) von Net-Order Host-Order (n h) • Beispiel: Portnummer in Adressdatenstruktur eintragen servaddr. sin_port = htons(PORTNUMBER); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 16
Byte-Funktionen memxxx() 1 • in den Socket-Adressdatenstrukturen gibt es verschiedene Bytefelder, die mit den normalen Integeroperationen und –funktionen nicht behandelt werden können • dazu stehen einige Funktionen zum Bearbeiten von Bytefeldern zur Verfügung, die in string. h deklariert sind #include <string. h> • void *memset(void *s, int c, size_t n); füllt n Bytes in Puffer s mit dem Byte c • void *memcpy(void *d, void *s, size_t n); kopiert n Bytes von Puffer s nach Puffer d die Puffer dürfen nicht überlappen bei Überlappung memmove() verwenden • int memcmp(void *s 1, void *s 2, size_t n); vergleicht n Bytes aus Puffer s 1 mit Puffer s 2 TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 17
IPv 4 -Adresskonvertierung 1 • IPv 4 -Adressen werden oft als String (dotted quad) in der Form 192. 168. 11. 123 dargestellt, sind aber eine 32 -Bit-Zahl • zur Konversion von IPv 4 -Adressen werden dienen die Funktionen inet_aton() und inet_ntoa() #include <sys/types. h> #include <sys/socket. h> #include <netinet/in. h> #include <arpa/inet. h> • int inet_aton(char *ptr, struct in_addr *inp); konvertiert IP-String ptr 32 -Bit-Adresse, die im struct in_addr inp gespeichert wird return ≠ 0 wenn ok, 0 bei Fehler • char *inet_ntoa(struct in_addr ip); konvertiert 32 -Bit-Adresse String, NULL bei Fehler TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 18
Netzwerk-Informationen 1 • Netzwerkinfos, z. B. Hostnamen, Dienste, usw. Anwendungsprogramm Abfrage Host-/Servernamen? Abfrage Dienstinfos/-ports? Resolver Betriebssystem /etc/hosts DNS NIS TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI /etc/services 19
Netzwerk-Informationen 2 • Hostnamen werden geliefert von – DNS-Service (verteiltes Datenbanksystem) – NIS-Services (Netzwerk-Datenbanksystem) – lokale Datei /etc/hosts – Management mittels sog. Resolver • Auszug aus /etc/hosts # IP-Addresse 127. 0. 0. 1 193. 174. 26. 49 193. 174. 26. 111 193. 174. 26. 112 193. 174. 26. 113 193. 174. 26. 114 Hostname Aliasnamen localhost maya loghost maya. rz. hs-fulda. de hosta 1 hosta 2 hosta 3 hosta 4 hostb 1 TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 20
Netzwerk-Informationen 3 • Dienstinformationen werden geliefert von – NIS-Services – lokale Datei /etc/services • Auszug aus /etc/services # Name Port/Protocol Aliases tcpmux 1/tcp echo 7/udp ftp-data 20/tcp ftp 21/tcp ssh 22/tcp telnet 23/tcp smtp 25/tcp mail http 80/tcp www http 80/udp www TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI Kommentare # Secure Shell # Mailservice # Webservice 21
IPv 4 -Namensauflösung 1 • Computer und Dienste werden i. d. R. über Namen (DNSNamen, Service-Namen) statt Nummern angesprochen • Host-/Servernamen sind meist bekannt und können mittels Funktionen abgefragt werden #include <netdb. h> // deklariert Typen usw. • Abfrage des Hostnamens struct hostent *gethostbyname(char *hostname); • hostent-Datenstruktur struct hostent {Offizieller Name char **h_aliases; // Feld char *h_name; // von Aliasnamen int h_addrtype; // Adresstyp int h_length; // Länge Adresse char **h_addr_list; // Liste von IP-Adressen }; TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 22
IPv 4 -Namensauflösung 2 • nützliches Makro für die erste IP-Adresse: #define h_addr_list[0] • für IPv 4 enthält h_addrtype immer den Typ AF_INET und h_addrlength immer den Wert 4 • h_name offiziellen Hostname (A-Record) • h_aliases Liste von Aliasnamen • h_addr_list Liste von IP-Adressen, die dem Host zugeordnet sind (A-Records), wobei h_addr dann die erste (und oft einzige) IP-Adresse des Host enthält • Beispiel: Abfrage der IPv 4 -Adresse für Host hostname hostptr = gethostbyname(hostname); if(hostptr != NULL) // ok printf("IP: %s", inet_ntoa(*(struct in_addr *) hostptr->h_addr); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 23
IPv 4 -Namensauflösung 3 • Funktion zur Ermittlung des Hostnamens bei bekannter IPv 4 Adresse (~ reverse lookup) struct hostent *gethostbyaddr(char *addr, int len, int type); • Ermittlung des eigenen Hostnamens int gethostname(char *hostname, int len); • Dienst / Portnummer ermitteln #include <netdb. h> struct servent { char *s_name; // offizieller Servicename char **s_aliases; // Liste von Aliasnamen int s_port; // Dienstnummer = Port char *s_proto; // Protokoll }; struct servent *getservbyname(char *serv, char *prot); • Beispiel: Ermittlung des Webservice-Ports struct servent *se = getservbyname("http", "tcp"); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 24
IPv 4 -Namensauflösung • 4 INET-HTTP-Verbindung mit Host- und Servicenamen einrichten #include #include <stdio. h> <sys/types. h> <sys/socket. h> <netdb. h> <netinet/in. h> <arpa/inet. h> <string. h> // // // Datentypen Socketdeklarationen -> gethostbyname(), getservbyname() -> sockaddr_in -> inet_ntoa() -> memset(), memcpy() . . . struct sockaddr_in addr; struct hostent *host; struct servent *serv; . . . // Host- und Servicedaten ermitteln -> IP-Adresse und Port host = gethostbyname(hostname); serv = getservbyname("http", "tcp"); // Port in Net-Order! printf("%sn", inet_ntoa(*(struct in_addr *) host->h_addr), ntohs(serv->s_port)); // Serveradress-Datenstruktur einrichten memset((void *) &addr, 0, sizeof(addr)); addr. sin_family = AF_INET; addr. sin_port = serv->s_port; memcpy((void *) &addr. sin_addr. s_addr, (void *) host->h_addr, sizeof(addr)); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 25
Unix-Domain-Socket 1 • Sockets in der Unix Domain (Adressfamilie AF_UNIX oder AF_LOCAL) werden als auch Unix Sockets oder lokale Sockets bezeichnet • nur innerhalb eines Unix-Systems wirksam • verankert und angesprochen über einen Dateinamen • vergleichbar den Named Pipes • Adressdatenstruktur für Unix-Sockets struct sockaddr_un #include <sys/types. h> #include <sys/socket. h> #include <sys/un. h> // Adresse für lokale Unix-Domain struct sockaddr_un { sa_family_t sun_family; // Adressfamilie char sun_path[]; // Socket-Pfadname } TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 26
Einfacher Unix-Domain-Client 1 • Programmablauf eines einfachen Unix-TCP-Clients – – – Erzeugen eines Unix-TCP-Sockets Einstellen des Sockets auf Serveradresse mit Socketpfadnamen Verbindung zum Server aufbauen Nachricht an Server senden Trennen der Verbindung durch Schließen des Sockets • Includes #include #include <stdio. h> <sys/types. h> <sys/socket. h> <sys/un. h> <string. h> // // // Standardein-/ausgabe Typdefinitionen Socketdefinitionen Unix-Socket-definitionen Stringfunktionen • Makros // Pfadname des Sockets #define SOCK_PATH "/tmp/tp-unsocket" TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 27
Einfacher Unix-Domain-Client 2 • Variable int sock_fd; // Socket-Filedescriptor struct sockaddr_un server_addr; // Serverdaten char buffer[512]; // Schreib-/Lesepuffer int nbytes, rval, length; // Laengenvariablen • Erzeugen eines Unix-TCP-Sockets sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); if(sock_fd < 0) // Fehler beim Erzeugen. . . • Einstellen des Sockets auf Serveraddresse // Datenstruktur server_addr auf 0 initialisieren memset((void *)&server_addr, 0, sizeof(server_addr)); // Adressfamilie Unix eintragen server_addr. sun_family = AF_UNIX; // Socketpfadnamen eintragen strcpy(server_addr. sun_path, SOCKET_PATH); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI • 28
Einfacher Unix-Domain-Client 3 • Verbindung zum Server aufbauen rval = connect(sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)); if(rval < 0) // Fehler Verbindungsaufbau. . . • Nachricht bauen und an Server senden sprintf(buffer, „Hallo Server. PID %d", getpid()); length = strlen(buffer); nbytes = write(sock_fd, buffer, length); if(nbytes != length) // Fehler beim Senden. . . • Trennen der Verbindung durch Schliessen des Sockets close(sock_fd); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 29
Einfacher Unix-Domain-Server 1 • Programmablauf eines einfachen Unix-TCP-Server – – – Erzeugen eines Unix-TCP-Sockets Einstellen des Sockets auf Serveradresse mit Socketpfadnamen Socket binden und Warteschlange einrichten Nachricht von Client lesen und ausgeben Trennen der Verbindung durch Schließen des Sockets • Includes #include #include <stdio. h> <sys/types. h> <sys/socket. h> <sys/un. h> <string. h> // // // Standardein-/ausgabe Typdefinitionen Socketdefinitionen Unix-Socket-definitionen Stringfunktionen • Makros // Pfadname des Sockets #define SOCK_PATH "/tmp/tp-unsocket" TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 30
Einfacher Unix-Domain-Server 2 • Variable int server_fd, client_fd; // Socket-Filedescriptoren struct sockaddr_in server_addr, client_addr; char buffer[512]; // Schreib-/Lesepuffer int nbytes, rval, length; // Zaehlervariablen • Erzeugen eines Unix Server TCP-Sockets server_fd = socket(AF_UNIX, SOCK_STREAM, 0); if(server_fd < 0) // Fehler beim Erzeugen. . . • Einstellen des Sockets auf Serveraddresse // Datenstruktur server_addr auf 0 initialisieren memset((void *) &server_addr, 0, sizeof(server_addr)); // Adressfamilie Unix eintragen server_addr. sun_family = AF_UNIX; // Verbindung von beliebigen Clientadressen strcpy(server_addr. sun_path, SOCK_PATH); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 31
Einfacher Unix-Domain-Server 3 • Socket an Server-Adressstruktur binden bind(server_fd, (struct sock_addr *) &server_addr, sizeof(server_addr)); • Warteschlange einrichten, Empfangsbereitschaft zeigen listen(server_fd, 5); • in Endloschleife Verbindungen von Clients annehmen client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &length); if(rval < 0) // Fehler Verbindungsaufbau. . . • Nachricht von Client lesen und ausgeben bytes = read(client_fd, buffer, length); if(nbytes != length) // Fehler beim Senden. . . • Socket schliessen und weiter in Endlosschleife close(client_fd); TCP/IP-Programmierung - Peter Klingebiel - HS Fulda - FB AI 32
- Slides: 32