Beispiel Block 1 Programmierrichtlinien und Fehlerbehandlung Argumentbehandlung Stream I/O Signale Sockets Übungsaufgaben Benedikt Huber SS 2012 1 Übungsbeispiel 1 Allgemeines Programmerstellung 2 Entwicklung der Übungsbeispiele • Konventionen Programmerstellung • Verwendung von Makefiles • Übersetzten mit –Wall • Konventionen Argumentbehandlung • Verwenden von getopt • “Usage” Meldungen • Konventionen Fehlerbehandlung und Exitcode • Abfragen von Rückgabewerten • Ausgabe von Fehlern und Beenden des Programms 3 Programmerstellung a.h b.h x.c y.c • Makefiles verwenden • make all, make clean • Abhängigkeiten spezifieren • Übersetzen • -c … Übersetzen • -Wall … Aktivieren aller Warnungen • -g ... Informationen für Debugger • Linken • Objektdateien ausführbare Datei Übersetzer x.o y.o Linker executable 4 Programmerstellung (2) • Konfigurieren des Übersetzers • C99 Standard CC=gcc CFLAGS=-std=c99 –pedantic –g –Wall \ -D_XOPEN_SOURCE=500 –D_BSD_SOURCE • Abhängigkeiten • Steuern inkrementelle Übersetzung • Extrahieren von Abhängigkeiten mit gcc –M hello.o: hello.c hello.h 5 Programmerstellung (3) • Verwendung automatischer Variablen executable: $(OBJECT_FILES) $(LD) $(LDFLAGS) –o $@ $^ %.o: %.c $(CC) $(CFLAGS) –c –o $@ $< • Entfernen automatische erstellter Dateien clean: rm –f $(OBJECT_FILES) executable .PHONY: clean 6 Übungsbeispiel 1 Argumentbehandlung Fehlerbehandlung 7 Argumentbehandlung • Shell-Kommandos bestehen aus • Programmnamen, oder Pfad zur ausführbaren Datei • Optionen: steuern wie das Kommando angewendet wird. • Auch Optionen können Argumente besitzen. Argumenten: Objekte, auf die das Kommando angewendet werden soll (Dateien, Verzeichnisse, etc.) • Beispiel: ls –l -a Optionen /usr/local/bin /usr/bin Argumente 8 Argumentbehandlung POSIX-Konventionen (1) • Optionen vor restlichen Programmargumenten • Option Kurzform: „-“ gefolgt von einem Zeichen • Beispiel: -c • Zusammenfassen mehrerer Optionen möglich (-la) • Option Langform: Zeichenkette folgend „--“ • Beispiel: --dry-run • Reihenfolge der Optionen üblicherweise belanglos [1] hLp://pubs.opengroup.org/onlinepubs/9699919799/ 9 Argumentbehandlung POSIX-Konventionen (2) • Argumente zu Optionen • Bsp: hallo -a optarg1 -c arg1 arg2 • Bsp: hallo --user=max arg1 arg2 • Ende der Optionsliste: • Zeichenkette ist (a) kein Argument einer Option und (b) beginnt nicht mit “-” bzw. “--” Zeichenkette “--”, nicht als Argument einer Option • • Optionen dürfen in der Regel maximal einmal auftreten • Bsp: hallo -c -c arg1 # Fehler 10 Argumentbehandlung Dokumentation (man pages) • Optionale Angaben durch [] gekennzeichnet hallo -a optarg [-o] arg1 • Abhängigkeiten von Optionen hallo [-a optarg [-o]] arg1 • Alternative Optionen durch [x|y] hallo [-a optarg| -o] arg1 • Ein oder mehrere Vorkommen eines Arguments hallo –arg optarg file... 11 Argumentübergabe an main (1/2) int main(int argc, char **argv) • argc ... Anzahl der Elemente von argv • argv ... Array von Kommandozeilenparametern (argv[0]... argv[argc-1]) argv h a l l o \0 - a \0 a r ... 12 Argumentübergabe an main (2/2) • Welchen Wert hat argc? hallo hallo hallo hallo test -a argZuOpt –a argZuOpt -o test –a argZuOpt “–o test“ 13 Argumentübergabe an main (2/2) • Welchen Wert hat argc ? • Anzahl Kommandozeilenparameter + 1 hallo hallo hallo hallo test -a argZuOpt –a argZuOpt -o test –a argZuOpt “–o test“ 2 3 5 4 14 getopt (1/2) • Zur Optionsbehandlung wird die Funktion getopt verwendet (Langform: getopt_long) • Parameter für getopt • argc, argv, Spezifikation der gültigen Optionen • Aufgaben des Programmierers • Vorkommens einer Option zählen • Behandlung von ungültigen Optionen • Speichern von Optionsargumenten • Überprüfung der korrekten Argumentzahl 15 getopt Beispiel int c; while( (c = getopt(argc, argv, “a:o”)) != -1 ){ switch( c ){ case ‘a’: /* Option mit Argument */ break; case ‘o’: /* Option ohne Argument */ break; case ‘?’: /* ungültiges Argument */ break; default: /* unmöglich */ assert( 0 ); } } 16 getopt: Zählen von Optionen int opt_o = 0; ... case ‘o’: opt_o++; break; ... ... if( opt_o > 1 ) /* max. 1 Mal */ usage(); if( opt_o != 1 ) /* oder: genau 1 Mal */ usage(); 17 getopt: Argumente zu Optionen char *input_file = NULL; while( (c = getopt(argc, argv, “a:o”)) != -1) { switch( c ) { case ‘a’: /* optarg: Zeiger auf Optionsargument */ input_file = optarg; break; ... } } 18 Weitere Informationen: man pages • Beispielreferenz auf man‐page: getopt(3) • Bedeutung: “Die Informa-onen finden sie in den man pages zu getopt in • • • • Abschni: 3“ Lesen der manpage unter Linux: "man 3 getopt" Verschiedene man‐Pages mit dem gleichen Themen‐Namen: • getopt(1) der Shell‐Befehl • getopt(3) das C‐Kommando Kommandozeilenprogramme in AbschniL 1 C‐Kommandos in AbschniL 2 (Systemaufrufe), 3 (Bibliotheksaufrufe) und 7 (Verschiedenes) 19 Übungsbeispiel 1 Fehlerbehandlung Signalbehandlung 20 Umgang mit Fehlern (1/2) • Fehlervermeidung durch Programmierstil • Fehlermeldungen auf stderr • Rückgabewert von Funktion immer abfragen • Ausnahme: Bei Ausgabe auf stderr • Beim Auftreten eines Fehlers • Recovery – Strategie • In dieser LVA: ordnungsgemäßes Terminieren (alle Ressourcen schließen, ...) 21 Umgang mit Fehlern (2/2) • Aussagekräftige Fehlermeldungen • Probleme in welchem Programm? (argv[0]) • Welche Probleme? (z.B. fopen failed) • Ursache? (strerror(errno)) • Terminieren des Programms • Freigeben von Ressourcen (z.B. temporäre Dateien) • Exitcode (EXIT_SUCCESS, EXIT_FAILURE) 22 Usage-Meldungen char *command= “<not set>“; int main (int argc, char *argv[]) { if(argc>0) command = argv[0]; ... } void usage(void) { (void) fprintf(stderr,“Usage: %s [-a arg] arg1”, command); exit(EXIT_FAILURE); } 23 Signale • Signal ... asynchrone Prozessbenachrichtigung • Beispiele • Speicherschutzverletzung (SIGSEGV) • Interrupt vom Keyboard (SIGINT, <Ctrl>-C) • Abruptes Beenden eines Prozesses (SIGKILL) • Beenden eines Servers • Erzeugen von Signalen • kill 1521 (SIGTERM) • kill -9 1521 (SIGKILL) • man 7 signal 24 Signalbehandlung • Signalbehandlung • Für viele Signale kann Reaktion des Programms • • konfiguriert werden (Ausnahmen: SIGKILL,...) Optionen: Ignorieren, Terminieren, eigene Routine Unter Linux wird sigaction zum Konfiguieren der Signalbehandlung empfohlen • Betriebssystemroutinen • Signale unterbrechen Ausführung von • • Betriebssystemroutinen Option 1: Transparenter Neustart (BSD Semantik) Option 2: Fehlercode EINTR (SysV Semantik) 25 Signalbehandlungroutinen • C-Funktion, Signalnummer als Parameter void cleanup(int signal) { /* free ressources and terminate */ } • Einschränkungen • Kommunikation mit eigentlichem Programm über speziell • • definierte Variablen (volatile sig_atomic_t) Nur bestimmte Funktionen sind zugelassen Signal kann durch weiteres Signal unterbrochen werden (Abhilfe: sigprocmask) 26 Signalbehandlung konfigurieren • Signal mit Routine verbinden • • • • Signalbehandlungsroutine Behandeln von unterbrochenen Betriebssystemroutinen Blockieren von weiteren Signalen Siehe man 2 sigaction struct sigaction s; s.sa_handler = cleanup; memcpy(&s.sa_mask, &blocked_signals, sizeof(s.sa_mask)); s.sa_flags = SA_RESTART; sigaction(SIGINT, &s, NULL); 27 Signalbehandlungsroutinen (2) • Globale Variablen im Signalhandler volatile sig_atomic_t count; void signal_handler_count(int signal){ count++; } • Synchronisation • Signalbehandlungsroutine soll u.U. nur einmal • ausgeführt, oder an einer kritischen Stelle nicht unterbrochen werden Vorrübergehendes Deaktivieren von Signalen 28 Übungsbeispiel Block 1 Stream I/O Christian El Salloum SS 2010 29 Aufbau des Dateisystems (Linux) Hierarchische Struktur / von Dateien Verschiedenste Dateitypen in einer gemeinsamen Verzeichnisstruktur: „everything is a file“ - - - - - - /bin commands /lib /dev devices /etc /usr /var startup and configuracon files /man /local plain files (stream of characters) directories (Interpretacon durch das OS) character‐, block special files (Geräte; z.B. Terminal, FestplaLe) named pipes sockets (z.B. TCP/IP sockets, UNIX domain sockets) symbolic links (Verweise) 15.03.12 30 Mounten von Dateisystemen Zusammenfassen mehrerer File‐Systeme in einer Verzeichnisstruktur Eingebundenes Dateisystem ist entweder: - lokal verfügbar (z.B. untersch. Parccon oder FestplaLe, Wechseldatenträger), - verfügbar via Netzwerk (z.B. über NFS), - oder befindet sich selbst in einem File (z.B. loop device für ISO‐Images) Vorteil: untersch. Filesysteme gleichzeicg verwendbar 15.03.12 31 Virtuelles Filesystem (VFS) > 15 physikalische Filesysteme unter Linux in Verwendung - Kompacbilität zu anderen Systemen (z.B. NTFS, FAT) - Sicherheit, Zuverlässigkeit der Daten (z.B. Ext3, ReiserFS) - Performance (z.B. XFS) Einführung einer zusätzlichen Abstrakconsebene - einheitliche SchniLstelle - transparentes Mounten verschiedener physikalischer Dateisysteme (Parcconen) in eine Directory‐Struktur 15.03.12 32 File Descriptors Verweis auf Eintrag in Tabelle offener Dateien (file descriptor table, Teil des aktuellen Prozesses) Standard I/O STDIN_FILENO = 0 (Standardeingabe) STDOUT_FILENO = 1 (Standardausgabe) STDERR_FILENO = 2 (Fehlerausgabe) Siehe auch: fileno(stdin), fileno(stdout), ... Funkconen: open(2), close(2), read(2), write(2),... 33 Stream IO in C Stream IO baut auf File‐Deskriptoren auf #include <stdio.h> Stream Datentyp: FILE Gepuffert (siehe fflush(3)) Konvencon: Befehle beginnen mit ‚f‘ fopen(3), fdopen(3), fwrite(3), fprinA(3), … stdin, stdout, stderr sind vordefinierte Streams S t r eam I O Puf f er IO OS 34 fopen(3) FILE *fopen(const char *path, const char *mode); Die Datei path wird geöffnet, und mit Stream (Rückgabewert) verbunden mode: „r“ nur lesen “w“ nur schreiben (exiscerenden Inhalt löschen) “a“ nur schreiben (am Ende anhängen) „r+“/“w+“/“a+“ lesen und schreiben 35 fdopen(3) FILE *fdopen(int fildes, const char *mode); Assoziiert einen Stream mit einem Filedescriptor FILE* f; int fd; int fd = socket(AF_INET, SOCK_STREAM,0); … f = fdopen(fd, “r+“); fprintf(f,“Meine Prozess‐ID ist: %d\n“, getpid()); 36 fflush(3), fclose(3) int fflush(FILE *stream); int fclose(FILE *stream); fflush erzwingt das Schreiben von gepufferten Daten fclose ruv fflush auf und schließt den Stream sowie den zugrundeliegenden Deskriptor. 37 Lesen/Schreiben Funk-on fread Lesen von n Elementen a size Bytes fgets Lesen einer Zeile fgetc Lesen eines Zeichens fwrite Schreiben von n Elementen a size Bytes fputs Schreiben eines C‐Strings fprinx Formacertes Schreiben fputc Schreiben eines Zeichens fseek Posiconieren des Dateiposiconszeigers 38 ferror(3), feof(3) int ferror(FILE *stream); int feof(FILE *stream); int clearerr(FILE *stream); ferror ergibt den Fehlerstatus des Streams zurück (0 ~ error flag nicht gesetzt). feof fragt ab, ob das End‐Of‐File Flag des Streams gesetzt ist (wird beispielsweise von fgets gesetzt, wenn das Ende der Datei erreicht wird) clearerr löscht Fehlerstatus und EOF Flag 39 Stream I/O Beispiel #define SIZE 512 int main(int argc, char **argv) { char buffer[SIZE]; FILE *f; … f = fopen(argv[1],“r“); while (fgets(buffer,SIZE,f) != NULL) { fputs(buffer,stdout); } if (ferror(f)) { bail_out(“IO Error“); } return 0; } 40 Übungsbeispiel Block 1 Sockets Christian El Salloum SS 2010 41 Sockets Was sind Sockets? • Kommunikaconsmechanismus (nicht verwandte Prozesse, verschiedene Maschinen) Kommunikaconsendpunkt • Client und Server kommunizieren durch Lesen von und Schreiben auf den Dateideskriptor, der dem Socket zugeordnet ist Socket API Üblicherweise Schnittstelle zur Transportschicht eines Kommunikationsprotokolls • • • • • • Application Layer (HTTP, SMTP) Socket API Transport Layer (TCP, UDP) Socket API (Raw Sockets) Network Layer (IP, ARP) Link Layer (Ethernet Driver) Sockets (2) Adressfamilie (Network Layer) • AF_INET (IP), AF_INET6 man 7 ip • Unix Domain Sockets (Lokale Interprozesskommunikation) man 7 unix Verbindungsorientierte Sockets • SOCK_STREAM, Default für IP ist TCP • Verbindung wird eindeutig durch zwei Endpunkte identifiziert Verbindungslose Sockets • SOCK_DGRAM, Default für IP ist UDP Client/Server TCP/IP Protokoll Client und Server kommunizieren über das Senden und Empfangen von Bytefolgen Eigenschaften: Verbindungsorientiert, vollduplex, zuverlässig Endpunkt Clientseite Endpunkt Serverseite Client IP + freie Portnummer Server IP + Client Client host address Client IP Connection socket pair Server (port 80) Server host address Server IP Client-Server Beispiel Server Socket() Bind() Client Listen() Blockiert bis Verbindung hergestellt Accept() Recv() Send() Socket() Verbindungsau{au Connect() Daten (Anfrage) Send() Daten (Antwort) Recv() 46 System Call: socket Erstellt einen Kommunikationsendpunkt (Socket) int socket(int family, int type, int protocol) family: Adressfamilie (Protokollfamilie) • e.g. AF_UNIX, AF_INET type: Art der Socketkommunikation • e.g. SOCK_STREAM, SOCK_DGRAM • Nicht alle Kombinationen von Protokollfamilie und Typ werden unterstützt protocol: zu verwendendes Kommunikationsprotokoll • Protokollfamilie + Typ implizieren üblicherweise Protokoll • 0 Default-Protokoll Rückgabewert: Handle des neu erstellten Socket Client-Server Beispiel Server Socket() Bind() Client Listen() Blockiert bis Verbindung hergestellt Accept() Recv() Send() Socket() Verbindungsau{au Connect() Daten (Anfrage) Send() Daten (Antwort) Recv() 48 System Call: bind Ordnet dem neu erstellten Socket eine bestimmte Adresse zu int bind(int socket, struct sockaddr *address, socklen_t addr_len) Socket: identifiziert den neu erstellten Socket Address: Datenstruktur mit lokaler Adresse Socket Address Format Generische Datenstruktur für Socket - Adressen: • Argumente zu connect, bind, und accept struct sockaddr { unsigned short sa_family; /* protocol family */ char sa_data[14]; /* address data. */ }; IP – spezifische Datenstruktur: • (sockaddr_in *) muss auf (sockaddr *) gecastet werden struct sockaddr_in { unsigned short sin_family; /* address family (always AF_INET) */ unsigned short sin_port; /* port num in network byte order */ struct in_addr sin_addr; /* IP addr in network byte order */ unsigned char sin_zero[8]; /* pad to sizeof(struct sockaddr) */ }; Client-Server Beispiel Server Socket() Bind() Client Listen() Blockiert bis Verbindung hergestellt Accept() Recv() Send() Socket() Verbindungsau{au Connect() Daten (Anfrage) Send() Daten (Antwort) Recv() 51 System Call: listen Bei verbindungsorientierten Protokollen: Server ist bereit Verbindungen anzunehmen int listen(int socket, int backlog) socket: identifiziert den neu erstellten Socket backlog: Anzahl an Verbindungsanfragen die vom Betriebssysteme in einer Warteschlange verwaltet werden, bis der Server die Verbindungen annimmt (Richtwert: 5) Client-Server Beispiel Server Socket() Bind() Client Listen() Blockiert bis Verbindung hergestellt Accept() Recv() Send() Socket() Verbindungsau{au Connect() Daten (Anfrage) Send() Daten (Antwort) Recv() 53 System Call: accept Warten auf eingehende Verbindungen (passiver Verbindungsaufbau) int accept(int socket, struct sockaddr *address, socklen_t *addr_len) Blockiert bis zu einer Verbindungsanfrage eines Clients Rückgabewert ist ein Socket für die aufgebaute Verbindung (ein Dateideskriptor) System Call: connect Aktiver Verbindungsaufbau (Client) int connect(int socket, const struct sockaddr *address, socklen_t addr_len) Kehrt nach dem Handshake zum Aufrufer zurück address spezifiert die Adresse des Servers Das Betriebssystem des Clients wählt üblicherweise beliebigen, nicht verwendeten Port System Call: send and recv Nach dem Verbindungsaufbau können Daten versendet und empfangen werden Senden einer Nachricht über den spezifizierten Socket int send(int socket, const void *msg, size_t msg_len, int flags) Empfangen einer Nachricht vom spezifizierten Socket int recv(int socket, void* buf, size_t buf_len, int flags) System Call: send and recv Rückgabewert: Anzahl empfangener/versendeter Bytes • Möglicherweise weniger Bytes übertragen als • angefordert Schleife notwendig Übungsbeispiel Block 1 Übungsaufgaben Christian El Salloum SS 2010 58 1. Übungsbeispiel 1a: Implementierung eines einfachen UNIX Tools Kennenlernen der Programmiersprache C Argumentbehandlung Kennenlernen von Makefiles 1b: Server und Client für „mastermind“ Kommunikation via Sockets Implementierung eines einfachen Protokolls Source – Code für Server ist zum Teil vorgegeben Optional: Entwicklung einer Spielstrategie in C 15.03.12 1.Übungsbeispiel 59 Abwicklung des Spiels 1 Server, 1 Clients Kommunikation via TCP/IP Sockets Ziel: Erraten der geheimen Farbfolge Client übermittelt vermutete Folge (2 Byte) Server antwortet mit einem Byte Anzahl korrekt positionierter Farben Anzahl falsch positionierter Farben Statusflags (Ende des Spiels, Paritätsfehler) Einfaches binäres Protokoll 15.03.12 1.Übungsbeispiel 60 Ende des Spiels Spieler errät die Geheimfolge Ausgabe Anzahl gespielter Runden Erreichen der maximalen Anzahl von Runden (Server signalisiert Ende des Spiels) Paritätsfehler (z.B. falsch berechnet) Andere Fehler (z.B. Verbindungsfehler) 15.03.12 1.Übungsbeispiel 61 Bewertung: Korrekte Clients/Server Protokoll korrekt implementiert Einhalten der maximalen Antwortzeit (1 Sekunde) Korrekte Berechnung der Serverantwort Terminieren mit dem korrekten Exitcode Ausgabe der korrekten Anzahl gespielter Runden Korrekte Argumentbehandlung Korrekte Signalbehandlung Einhaltung der Richtlinien zu den Übungsbeispielen 15.03.12 1.Übungsbeispiel 62 Spielstrategien Optional: Implementierung von Spielstrategien Tipp: Einfacher Algorithmus gewinnt stets in 34 Zügen Bonuspunkte für gute und ausgezeichnete Implementierungen Gut (+5): Gewinnstrategie, im Schnitt 20 oder weniger Runden Ausgezeichnet (+10): Gewinnstrategie, im Schnitt 8 oder weniger Runden Viel Spass 15.03.12 1.Übungsbeispiel 63