PROGRAMMIEREN MIT UNIX/LINUX-SYSTEMAUFRUFEN UNIX/Linux-Interprozesskommunikation, zugehörige Systemaufrufe und Kommandos 12. UNIX/Linux-Sockets Wintersemester 2016/17
UNIX/Linux-IPC-Mechanismen Nachrichtenbasierter Informationsaustausch: 5. 1. Nachrichten(warte)schlangen ( message queues ) System V IPC 12. 2. Sockets BSD-UNIX Speicherbasierter Informationsaustausch: 6. 7. 8. 4. Gemeinsame Speicherbereiche( shared memory ) System V IPC 5. Pipes (Named Pipes und FIFOs) Version 7 Signale: 6. Asynchrone UNIX-Signale Version 7 Synchronisationsmechanismen: 11. 7. UNIX-Semaphore System V IPC ws2016/17 H.-A. Schindler Folie: 12-2
Nachrichtenbasierter Informationsaustausch Prozess 1 Prozess 2 Befehle Befehle Daten Daten Datentransport (Nachrichten) Expliziter Datentransport in Form von Botschaften (= messages) Aus einem Datenbereich wird in den anderen kopiert. Sendender Prozess: benutzt (Variante einer) Send-Funktion Empfangender Prozess: benutzt (Variante einer) Receive-Funktion ws2016/17 H.-A. Schindler Folie: 12-3
Sockets -Grundsätzliches Ursprung: erstmalig in BSD-UNIX 4.2 Bedeutung: sind rechnerübergreifend verwendbar (Internet!!), d.h. zur Kommunikation zwischen Prozessen auf verschiedenen (vernetzten) Rechnern (sog. verteilte Systeme) aber auch für IPC innerhalb nur eines Rechners damit universellster Kommunikationsmechanismus auch in anderen BS implementiert ws2016/17 H.-A. Schindler Folie: 12-4
Sockets zugehörige Systemaufrufe Umfang: Zu Sockets existiert das umfangreichste Sortiment an Systemaufrufen und Parametern. Da von UNIX-Systemen Sockets als (spezielle) Dateien angesehen werden, kann auch ein Teil der Funktionen für die Dateiarbeit verwendet werden. Beschränkung: wir beschränken uns zunächst auf die Funktionen: 1. socket(6) - Erzeugen eines Sockets 2. bind(6) - Anbinden einer zugehörigen Datenstruktur 3. connect() - Verbinden von 2 Sockets 4. send() - Senden einer Nachricht (bei verbundenen Sockets) 5. sendto(6) - wie send() auch ohne vorheriges connect() 6. recv() - Empfangen (bei verbundenen Sockets) 7. recvfrom() - Empfangen auch ohne vorheriges connect() ws2016/17 H.-A. Schindler Folie: 12-5
Zur Benutzung der Systemaufrufe Socket Prozess 1 (z.b. Client) Variante2a: ohne Verbinden: sendto(..) Erzeugen: socket(..) Anbinden: bind(..) Socket- Datenstruktur Variante1: Verbinden: connect(..) +send(..) +recv(..) Erzeugen: socket(..) bind(..) Prozess 2 (z.b. Server) Variante2b: ohne Verbinden: recvfrom(..) ws2016/17 H.-A. Schindler Folie: 12-6
Systemaufruf socket(..) erzeugt Socket und typisiert diesen Verwendung im Programm (Prinzip): int <id_sock; <id_sock = socket(<domain, <type, <protocol); Konkretes Beispiel: int id; id = socket(af_inet, SOCK_STREAM, 0); Parameter Symbol Typ Bedeutung Interpretation <id_sock int Rückkehrwert Identifikator des Sockets (auch: Socket-Handle) <domain int <type int <protocol int Zugehörigkeit zu bestimmter Gruppe Protokoll Adress-Familie oder Protokoll-Familie (20) AF_INET oder PF_INET: Internet-Socket AF_UNIX oder PF_UNIX: lokaler UNIX-Socket... Datenmodell + Verlässlichkeitseigenschaften SOCK_DGRAM:unzuverlässige, verbindungslose Kommunikation (normalerweise UDP) SOCK_STREAM:zuverlässige, verbindungsorient. Kommunikation (normalerweise TCP/IP) spezifisches Protokoll (meist System überlassen: 0) ws2016/17 H.-A. Schindler Folie: 12-7
verknüpft Socket-Datenstruktur mit erzeugtem Socket Systemaufruf bind(..) Verwendung im Programm (Prinzip): int <rb; <rb= bind(<id_sock, &<my_socketaddr, <socketaddress_length); Parameter Symbol Typ Bedeutung <rb int Rückkehrwert Konkretes Beispiel: int rw; rw=bind(id_socket1, &client_socketaddr, sizeof(client_socketaddr)); Interpretation Rückkehrwert des Systemaufrufs socket(..) <id_sock int Identifikator <my_ socketaddr <socketaddress _length &(struct sockaddr) int Adresse der Datenstruktur, die mit Socket verknüpft wird Länge dieser Datenstruktur ws2016/17 H.-A. Schindler Folie: 12-8
Datenstruktur sockaddr Für unterschiedliche Adressfamilien unterschiedliche Datenstrukturen verwenden: Adressfamilie AF_UNIX: #define UNIX_PATH_MAX 108 struct sockaddr_un{ sa_family_t sun_family; /* AF_UNIX */ char sun_path[unix_path_max] /* UNIX-Pfadname */ }; Adressfamilie AF_INET: struct sockaddr_in{ sa_family_t sin_family; /* AF_INET */ unsigned short sin_port; /* 16-Bit TCP- oder UDP-Port-Nr. */ struct in_addr sin_addr; /* 32-Bit IP-Adresse */ char sin_zero[8]; /* nur zum Auffüllen der Struktur */ }; /* einheitlich 16 Byte ( padding ) */ ws2016/17 H.-A. Schindler Folie: 12-9
Systemaufruf connect(..) Verbinden mit Socket des Kommunikationspartners (verbindungsorientierte Kommunikation) Verwendung im Programm (Prinzip): int <r; <r= connect(<id_sock, &<server_socketaddr, <socketaddr_length); Konkretes Beispiel: int rx; rx=connect(id_socket1, server_socketaddr, sizeof(struct sockaddr)); Parameter Symbol Typ Bedeutung Interpretation <r int <id_sock int Rückkehrwert Identifikator Rückkehrwert des Systemaufrufs socket(..) <server_ socket_addr &(struct sockaddr) Adresse der Datenstruktur des Sockets der Gegenstelle <socketaddr _length int Länge Länge der Datenstruktur ws2016/17 H.-A. Schindler Folie: 12-10
Systemaufruf send(..) Senden einer Botschaft (message) Verwendung im Programm (Prinzip): int <rs; <rs= send(<id_sock, &<message, <message_length, flags); Parameter Symbol <rs int Typ Bedeutung Rückkehrwert Konkretes Beispiel: int rw; rw = send(id_socket1, request, requestlength, 0); Interpretation <id_sock int Socket- Identifikator Rückkehrwert des Systemaufrufs socket(..) <message &char[ ] Anfangsadresse des Speicherbereichs im Programm, in welchem message spezifiziert <message _length int Länge Länge der message <flags zur Spezifikation spezieller Eigenschaften ws2016/17 H.-A. Schindler Folie: 12-11
Systemaufruf recv(..) Empfangen einer Botschaft (message) Verwendung im Programm (Prinzip): int <r; <r= recv(<id_sock, &<receive_buffer, <message_length, <flags); Konkretes Beispiel: int rw; rw = recv(id_socket1, buffer, bufferlength, 0); Parameter Symbol <rs int <id_sock int Typ Bedeutung Rückkehrwert Socket- Identifikator Interpretation Anzahl der tatsächlich empfangenen Byte Rückkehrwert des Systemaufrufs socket(..) <receive _buffer &char[ ] Anfangsadresse des Speicherbereichs im Programm, wo message zu deponieren <message _length int Länge Länge der message <flags zur Spezifikation spezieller Eigenschaften ws2016/17 H.-A. Schindler Folie: 12-12
Systemaufruf recvfrom(..) Empfangen einer Botschaft (message) und Abspeichern der Quelladresse Verwendung im Programm (Prinzip): int <r; <r = recvfrom(<id_sock, &<receive_buffer, <message_length, <flags, <source_address, <source_address_length); Konkretes Beispiel: int rw; rw = recvfrom(id_socket1, buffer, bufferlength, 0,...); Parameter Symbol <source address <source_ address_ length Typ struct sockaddr* socklen_t Bedeutung / Interpretation Wenn (<source_address NULL) wird Adresse der Quelle einer empfangenen Botschaft auf Paramter<source_address bereitgestellt, falls diese durch das verwendete Transportprotokoll zur Verfügung gestellt wird. Länge der Quell-Adresse Restliche Parameter: siehe recv()! ws2016/17 H.-A. Schindler Folie: 12-13
Systemaufrufe read( ) und write( ) Anstelle von send() kann auch write(),anstelle von recv() auch read() verwendet werden, beide aber ohne Parameter flags. Es gilt exakt die Syntax (= Aufruf im Programm ) von write()und read() für Arbeit mit Dateien. Verwendung im Programm (Prinzip): <x= read(<id_sock, &<receive_buffer, <message_length); <y= write(<id_sock, &<message, <message_length); Konkretes Beispiel: int u, v; u = read(id_socket1, buffer, bufferlength); v = write(id_socket2, &message, message_length); Parameter Symbol Typ Bedeutung <x, <y int Rückkehrwerte Interpretation Restliche Parameter: siehe send() bzw. recv()! ws2016/17 H.-A. Schindler Folie: 12-14
Beispielprogramm: server1.c #include <stdio.h #include <stdlib.h #include <sys/socket.h #include <sys/types.h #include <string.h #define socket_name_length 14 #define request_length 16 #define message_length 128 main() { int id_server_socket; // Socket-Handle (= Identifizierer) int rv_bind; // Rueckkehrwert von bind() int rv_connect; // Rueckkehrwert von connect() char server_socketname[socket_name_length] = /tmp/svsocket"; struct sockaddr server_socketaddr; char client_socketname[socket_name_length] = /tmp/clsocket"; struct sockaddr client_socketaddr; ws2016/17 H.-A. Schindler Folie: 12-15
N // zum Senden int rv_send; // Rueckkehrwert vom Senden char answer_buffer[message_length] = "Elbe"; // zum Empfangen int rv_receive; //Rueckkehrwert beim Empfangen char receive_buffer[request_length]; // Server-Socket erzeugen id_server_socket = socket(af_unix, SOCK_DGRAM, 0 ); N // Anbinden des Namens an Server-Socket server_socketaddress.sa_family = AF_UNIX; strcpy(server_socketaddress.sa_data, server_socket_name); rv_bind= bind(id_server_socket, &server_socket_address, sizeof(server_socket_address)); N ws2016/17 H.-A. Schindler Folie: 12-16
N // Warten auf Anfrage rv_receive=recv(id_server_socket, receive_buffer, request_length, 0); N // Nach einer Anfrage: Vorbereiten zum Antwort senden client_socketaddr.sa_family = AF_UNIX; strcpy(client_socketaddress.sa_data, client_socket_name); // Verbinden mit Server-Socket rv_connect= connect(id_server_socket, &client_socket_address, sizeof(struct sockaddr)); N // Senden rv_send = send( id_server_socket, answer_buffer, message_length,0); N // Sauber abschließen, Socket-Name entfernen unlink(server_socket_name); } ws2016/17 H.-A. Schindler Folie: 12-17
Sockets weitere Systemaufrufe weitere wichtige Aufrufe: listen(6) accept(6) zu Datenstruktur sockaddr für AF_INET Include-Dateien: #include <sys/socket.h #include <netinet/in.h ws2016/17 H.-A. Schindler Folie: 12-18
Datenstruktur sockaddr für AF_INET unterschiedliche Datenstrukturen für unterschiedliche Adressfamilien!! Adressfamilie AF_INET: struct sockaddr_in { sa_family_t sin_family; // AF_INET unsigned short sin_port; // 16-Bit TCP- oder UDP-Port-Nr. struct in_addr sin_addr; // 32-Bit IP-Adresse char sin_zero[8]; // nur zum Auffüllen der Struktur }; // einheitlich 16 Byte ( padding ) Beispiel: #define SERVER_PORT 4711 // Port-Nr. für Socket struct sockaddr_in ssock; // Einrichten der Datenstruktur ssock vom Typ sockaddr_in ssock.sin_family = AF_INET; // siehe oben ssock.sin_port = htons(server_port); // htons: konvertiert SERVER_PORT geeignet ssock.sin_addr.s_addr = INADDR_ANY; ws2016/17 H.-A. Schindler Folie: 12-19
Systemaufruf listen(..) legt Warteschlange für Server-Aufrufe an Verwendung im Programm (Prinzip): int <rl; <rl = listen(<id_sock, <number); Parameter Symbol Typ Bedeutung <rl int Rückkehrwert Konkretes Beispiel: int rw; rw = listen(id_socket1, 5); Interpretation <id_sock int <number int Beschreibung: Identifikator Rückkehrwert des Systemaufrufs socket(..) Anzahl gleichzeitig möglicher Anforderungen Über <number wird spezifiziert, wie viele Verbindungsanforderungen gleichzeitig akzeptiert werden. Dazu wird Warteschlange mit entsprechender Platzanzahl eingerichtet. Werden mehr als <number Verbindungen gleichzeitig gefordert, werden alle Anforderungen ab (<number + 1) zurückgewiesen ws2016/17 H.-A. Schindler Folie: 12-20
Stellt Empfangsbereitschaft eines Sever-Prozesses her Systemaufruf accept(..) Verwendung im Programm (Prinzip): int <r; <connect_id= accept(<id_serversocket, &<client_socket, &<clientsocket_length); Konkretes Beispiel: int xz; rw = accept(id_ssock, cl_socketaddr, &sizeof(struct sockaddr); Parameter Symbol Typ Bedeutung / Interpretation <connect_id int Rückkehrwert(Erfolgkontrolle und Verbindungd-ID) <id_ serversocket &<client _socket &<clientsocket _length int &(struct sockaddr) int Rückkehrwert des Systemaufrufs socket(..) Adresse der sockaddr-datenstruktur der Gegenstelle Länge einer Socket-Adresse accept() blockiert bis tatsächlich ein Aufruf stattfindet ws2016/17 H.-A. Schindler Folie: 12-21
Aufgabenstellung (Anregungen) 1. Analysieren Sie server1.c und client1.c. Lesen Sie dabei auch die Kommentare. Übersetzen Sie beide Programme und beobachten Sie deren Arbeit. (Server und Client dazu in jeweils eigenen Kommandofenstern starten! Reihenfolge beachten!) Die angelegten Sockets sind ähnlich wie benannte Pipes im Dateisystem sichtbar (bevor sie wieder gelöscht werden). Analysieren Sie z.b. nach dem Serverstart die entsprechenden Stellen im Dateisystem. Bei server2.c wurden recv() durch read() und send() durch write() ersetzt. 2. Übersetzen Sie server2.c und überprüfen Sie, ob auch dieser mit dem ungeänderten client1.c zusammenarbeiten kann. In server2b.c wurde als einzige Änderung gegenüber server1.c das Datenmodell des Sockets von SOCK_DGRAM auf SOCK_STREAM gesetzt. 3. Übersetzen Sie server2b.c und beobachten Sie! (Es reicht den Server zu starten.) ws2016/17 H.-A. Schindler Folie: 12-22
Auch server3.c ist eine Modifikation von server1.c. Es wurde eine einfache Schleife implementiert, so dass der Server wiederholt auf Anfragen antworten kann. (Für einen echten Server fehlt jedoch noch die Fähigkeit, die Anfragen zu analysieren und zur jeweiligen Anfrage passend zu antworten.) 4. Übersetzen Sie server3.c. Starten Sie verschiedene Clients, die Anfragen an den Server stellen und beobachten Sie. Ursprung aller Clients kann natürlich immer das Programm client1.c sein. ws2016/17 H.-A. Schindler Folie: 12-23
server4.c ist eine Modifikation von server3.c, wobei jetzt Internet-Sockets und eine Port-Nummer Anwendung finden. Anstelle von bind() und connect() werden jetzt listen() und accept() verwendet, die eine ähnliche Funktion haben. 5. Analysieren Sie server4.c. Übersetzen und starten Sie danach dieses Programm. Ein funktionsfähiges Programm muss blockieren, bis eine Anfrage kommt. Ob server4wirklich am Port 4711lauscht, kann durch das Kommando netstat a grep 4711 untersucht werden. Ein simpler Client kann dann mittels Kommando telnet<server_name 4711simuliert werden. (Wird das Kommando auf der gleichen Maschine eingegeben, auf der auch der Server läuft, ist telnetlocalhost4711 zu verwenden). Es werden dann die an der Tastatur eingegebenen Zeichen als Anfrage an server4übertragen. ws2016/17 H.-A. Schindler Folie: 12-24