4. Aufgabe
Zur Vorbereitung auf den Termin lesen Sie bitte das Skript zum Block E und machen Sie sich mit den C-Funktionen, die benötigt werden, vertraut (in welcher C-Bibliothek sind sie zu finden, welche Parameter benötigen sie und welche Information liefert ihr Rückgabewert). Es ist außerdem empfehlenswert sich mit der kurzen Übung intensiver zu beschäftigen. Einen Shortcut zu der Funktionsübersicht finden sie auch in der Navigation rechts.

Frischen Sie ihre C-Kenntnisse auf. Ein gute Einführung finden Sie hier:


Empfehlenswert sind auch die Folien der Vorlesung der Unit 2 (Communication Services and Protocols).
Wer tiefer in die Socket-Programmierung einsteigen möchte, dem sei das Buch „TCP/IP Sockets in C: Practical Guide for Programmers“ von Michael J. Donahoo und Kenneth L. Calvert empfohlen. Auch das Buch „Linux-UNIX-Programmierung“ von Jürgen Wolf (Kapitel 11) ist empfehlenswert. „Beej′s Guide to Network Programming“ und Perkins „NS3 Lab 1 – TCP/IP Network Programming in C“ sind ebenfalls empfehlenswerte Referenzen.




Auf alle Fälle ist es empfehlenswert, einige Programmbeispiele zu studieren, die in einer Vielzahl im Internet zu finden sind.

Ihre Aufgabe ist es, einen Stream Socket in der Programmiersprache C zu implementieren. Der Client soll Text, der über die Tastatur eingegeben wird, zu einem Server senden, der den Text auf dem Bildschirm ausgibt. Verwenden Sie dazu bitte die zur Verfügung gestellten Dateien stream_client.c bzw. stream_server.c und Makefile. Bevor Sie mit der Implementierung beginnen, müssen Sie die folgenden Aspekte berücksichtigen:
- Ihr Programm muss in der Lage sein, Daten von verschiedenen Quellen – von der Konsole und dem Socket – zu empfangen und zu senden. Um dies zu ermöglichen, wird eine kleine Zustandsmaschine benötigt.
- Um Ihnen die Prozeduren für den Zugriff auf den Socket zu erleichtern, ist ein Programmgerüst zur einfacheren Handhabung und Strukturierung vorhanden. Das Gerüst dient einzig alleine zu Lehrzwecken. Eine solche Implementierung wird in der Realität keine Anwendung finden. Im weiteren Verlauf des Praktikums werden Sie jedoch reale Implementierungen kennenlernen.
4.1 Programmgerüst
In der Regel ist es kein Problem, die Abfolge von Funktionen zu programmieren. Es gestaltet sich als viel schwieriger, den Funktionen die korrekten Parameter in der korrekten Darstellung zu übergeben. Werden hierbei Fehler gemacht, passiert gar nichts ohne, dass der Grund zunächst erkennbar ist. Die Korrektur der Fehler benötigt in solchen Fällen sehr viel Zeit. Um Ihnen dies zu vereinfachen, existiert bereits ein fertiges Programm. Sie können nun in diesem Programm einzelne Programmteile durch Ihren eigenen Quelltext ersetzen. Hierzu ist in jeder Funktion in den oben genannten Dateien ein Schalter vorhanden. Wenn der Schalter auf 1 gesetzt wird, verwenden Sie ihren eigenen Quellcode (siehe folgenden Beispielcode), wenn er ungleich eins ist, wird der vorbereitete Code ausgeführt.
#if 1 void someFunctionHeader( void someParameters ) { //TODO irgendwas } #endif
Dadurch werden Sie in die Lage versetzt, selektive Änderungen durchzuführen und deren Auswirkungen zu untersuchen. Wie Sie am Beispielcode erkennen, ist der Schalter mittels einer Präprozessor-Anweisung realisiert. Der Code zwischen den #if- und #endif-Anweisungen wird nur ausgeführt, wenn der Wert der #if-Anweisung 1 ist. Wenn der Wert 0 ist, dann wird eine Funktion in der vorhandenen Bibliothek des Programmgerüsts verwendet.
Zu Beginn steht in allen #if-Anweisungen der Wert 0. Das Programm ist somit von Anfang an kompilierbar und lauffähig und kann durch Änderung der entsprechenden Quelltextzeilen zu 1 sukzessive durch eigene Implementationen ersetzt werden. Hierdurch soll Ihnen die Möglichkeit gegeben werden, eigene Funktionen und vorgegebene Funktionen in beliebiger Konstellation zu laden, was die Fehlersuche stark vereinfachen sollte. Die genauen Funktionsbeschreibungen und Aufgabenstellungen sind in der gegebenen Quelltext-Datei aufgeführt, dennoch soll hier ein kurzer Überblick, verbunden mit dem empfohlenen Arbeitsablauf gegeben werden.
4.1.1 Dateien und Verzeichnisse
Alle zur Verfügung gestellten Dateien für diesen Termin befinden sich als Kopie im ISIS und in Ihrem Home-Verzeichnis im Ordner „StreamSocket“. In diesem Ordner befinden sich der Ordner lib und die Dateien Makefile, stream_client.c und stream_ server.c, sowie die Dateien stream_client.h und stream_server.h.
Makefile: Das Makefile erleichtert Ihnen das Übersetzen des Quellcodes zu einem ausführbaren Programm, indem es alle benötigten Anweisungen ausführt. Sie geben lediglich den Befehl
ein. Vor jedem neuen make-Befehl empfehlen wir make clean aufzurufen.
lib: Dieser Ordner enthält Bibliotheken mit vordefinierten Funktionen für gängige Betriebssysteme, die zur Verwendung des Clients/ Servers benötigt werden. Unterstützt werden Linux und MacOS X.
stream_client.c / stream_server.c: Diese Dateien enthalten den Quellcode des Clients und des Servers. Jede der beiden Dateien lässt sich im Wesentlichen in drei Abschnitte gliedern:
- Der erste Teilabschnitt enthält die verschiedenen Bibliotheken, die für die Verwendung von Sockets notwendig sind.
- Der zweite Abschnitt enthält die main-Funktion, welche die einzelnen Funktionen in der korrekten Reihenfolge aufruft. Außerdem werden hier die bei der Ausführung des Programms übergebenen optionalen Argumente (Help, Port und IP-Adresse) zur Weiterverarbeitung in Variablen geschrieben. Durch Eingabe des Programms mit der Option -h wird ein kurzer Hilfetext für die Bedienung des Programms ausgegeben.
- Im letzten Abschnitt erfolgt Ihre Implementation. Zu jeder Funktion ist angegeben, was sie tun soll, sowie der eventuelle Rückgabe-Parameter beschrieben.
4.2 Empfohlene Vorgehensweise
Folgende Vorgehensweise hat sich in der Vergangenheit bewährt:
- Implementieren Sie den TCP Client.
- Implementieren Sie den TCP Server.
- Machen Sie sich mit den Programmen stream_client und stream_server vertraut. Eine kleine Anleitung, wie das fertige Programm funktionieren soll und wie aus den Quelldateien ein ablauffähiges Programm erstellt wird, finden Sie im folgenden Video:
4.2.1 Vertrautmachen mit der Umgebung
Bevor die Programme stream_client und stream_server ausgeführt werden können, müssen Sie zunächst übersetzt und mit einem Linker gebunden werden. Dies geschieht automatisch durch Eingabe des Befehls make. Hierzu starten Sie zunächst das Programm Terminal. Hierauf öffnet sich ein Fenster mit einer Shell. In der Shell wechseln Sie in das Verzeichnis StreamSocket in ihren Home-Bereich und geben am Prompt den Befehl make ein.
Daraufhin werden die beiden Programme stream_client und stream_server erzeugt. Starten Sie zunächst das Programm stream_server. Der Server wird durch Eingabe des Befehls:
./stream_server <ip_adresse> <port>
gestartet. Die fest eingestellte ip_adresse für den Client ist das LoopBack-Interface (Adresse 127.0.0.1) und der Port 3850. Für den Server wird der Wert INADDR_ANY der Adresse zugewiesen. Optional kann der im Programm fest eingestellte port und die ip_adresse durch Angabe der Argumente verändert werden. Dies ist beispielsweise dann notwendig, wenn der Client und der Server auf verschiedenen Rechnern laufen.
Öffnen Sie nun ein neues Fenster im Programm Terminal. Wechseln Sie in das Verzeichnis StreamSocket und starten durch Eingabe des Befehls
./stream_client <ip_adresse > <port>
den Client.
Wenn Sie alle Anweisungen korrekt durchgeführt haben, können Sie wahlweise am Client oder am Server einen Text eingeben, der dann am jeweils anderen Fenster ausgegeben wird.
4.2.2 Implementation des TCP Clients
In diesem Abschnitt wird beschrieben, welche Stellen im Quelltext des Clients modifiziert werden müssen und welche Schritte zum Erstellen des Programms notwendig sind.
Auf den Rechnern im Labor stehen die folgenden Texteditoren zur Verfügung:
vi, nano, KWrite, gedit, notepad und Sublime Text
Um eine Datei mit einem Anderen als dem Standardprogramm zu öffen, klicken sie mit rechts auf die gewünschte Datei und wählen sie anschließend das Programm aus.
Eine Anleitung zum Editieren der Quelldateien mit unterschiedlichen Editoren finden sie in diesem Video.
- Funktion open_socket(): Die Funktion soll einen Socket erzeugen.
- Funktion connect_to_server(): Diese Funktion soll eine Verbindung zu einem Server aufbauen. Hierzu müssen Sie für die connect()-Funktion notwendigen Parameter konstruieren. Die Parameter werden mit der Funktion convert_ to_server_info() aufbereitet.
- Funktion convert_to_server_info(): Diese Funktion erzeugt aus seinen Parametern (Port und Adresse) die Socket-internen Adresskonstrukte (siehe 3.5).
- Funktion handle_connection(): Diese Funktion enthält die select()-Funktion, um gleichzeitig den Socket und Eingaben von der Tastatur (Standard Input File-Deskriptor) zu verwalten.
Sie haben die Möglichkeit, alle Änderungen in der Datei stream_client.c auf einmal oder jede der aufgeführten Funktionen einzeln zu modifizieren.
Empfehlenswert ist eine schrittweise Implementation. Achten Sie darauf, dass bei der modifizierten Funktion der Wert der #if-Anweisung von 0 auf 1 geändert werden muss. Ansonsten hat Ihre Modifikation keine Wirkung.
Nach jedem Schritt speichern Sie die Datei und führen das make-Kommando aus. Wenn das make-Kommando nicht erfolgreich ausgeführt wurde, müssen Sie den Quelltext korrigieren, bis das Kommando fehlerfrei abgeschlossen wurde. Anschließend testen Sie durch Ausführen des Programms und überprüfen, ob ihre Änderung das gewünschte Verhalten zeigt.
4.2.3 Implementation des TCP Servers
Die Schritte, die zum Erzeugen des Servers notwendig sind, entsprechen im Wesentlichen denen des Clients. Unterschiedlich ist lediglich der Name der Quell- und der Ausführbaren-Datei (stream_server.c bzw. stream_server).
Da der Server das Pendant des Client ist, sind in ihm vielen Funktionen identisch mit denen des Clients. Deshalb werden nur die zusätzlichen Funktionen beschrieben:
- Funktion listen_on_socket(): Diese Funktion versetzt den Socket in den listen-Zustand (siehe 4.2.1).
- Funktion accept_connection(): Diese Funktion nimmt eine Verbindungsanfrage an und erzeugt einen weiteren Socket, der diese Verbindung kontrolliert (siehe 4.2.1).