32
Seminararbeit im Rahmen des Studiengangs Scientific Programming Fachhochschule Aachen, Campus Jülich Fachbereich 9 – Medizintechnik und Technomathematik Nachrichtenbasierte Kommunikation zwischen wissenschaftlichen Applikationen 15. Dezember, 2018 Frederik Peters

Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

Seminararbeit im Rahmen des StudiengangsScientific Programming

Fachhochschule Aachen, Campus Jülich

Fachbereich 9 – Medizintechnik und Technomathematik

Nachrichtenbasierte Kommunikation zwischenwissenschaftlichen Applikationen

15. Dezember, 2018

Frederik Peters

Page 2: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul
Page 3: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

Eigenhändigkeitserklärung

Diese Arbeit ist von mir selbstständig angefertigt und verfasst. Es sind keine ande-ren als die angegebenen Quellen und Hilfsmittel benutzt worden.

(Ort, Datum) (Unterschrift)

Diese Arbeit wurde betreut von:

1. Prüfer: Prof. Ulrich Stegelmann2. Prüfer: Ingo Heimbach (FZJ, PGI/JCNS)

Sie wurde angefertigt in der Forschungszentrum Jülich GmbH im Peter GrünbergInstitut / Jülich Centre for Neutron Science.

Page 4: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul
Page 5: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

Im Peter Grünberg Institut / Jülich Centre for Neutron Science werden Experimen-te und Simulationen oftmals auf dedizierter Hardware, wie beispielsweise Supercom-putern, als langlebige Prozesse durchgeführt. Dementsprechend ist es in vielen Fällenwünschenswert, Zwischenergebnisse in einem separaten Prozess oder auf einem ent-fernten Computer zur Kontrolle visuell aufzubereiten. Um dieser wiederkehrendenAnforderung gerecht zu werden, wird daher in dieser Seminararbeit eine Bibliothekentwickelt, die beliebige Nachrichten zwischen mehreren Parteien über das Netzwerkübertragen kann.Der Nachrichtenfluss wird nach einem Request-Response-Muster aufgebaut. Da-

mit eine allgemeine Verwendbarkeit gewährleistet ist, kann der Nachrichtenaustauschdabei sowohl synchron als auch asynchron erfolgen, um das eigentliche Programmwährend der Ausführung nicht zu blockieren. Zudem wird die Bibliothek robust mitVerbindungsabbrüchen umgehen und diese zeitnah feststellen können.Hierauf aufbauend kann die Bibliothek im Rahmen einer Bachelorarbeit unter an-

derem um Many-to-Many-Kommunikation erweitert werden, um sowohl Nachrichtenmehrerer Sender zu sammeln als auch Daten an mehrere Empfänger verteilen zu kön-nen. Außerdem ist es wünschenswert Kanäle bidirektional einsetzen zu können, umbeispielsweise Steuerungsbefehle an das datenerzeugende Programm zurückgeben zukönnen.

Page 6: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul
Page 7: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

Inhaltsverzeichnis1 Motivation 1

2 Anforderungen 2

3 Grundlagen 33.1 Synchrone/Asynchrone Kommunikation . . . . . . . . . . . . . . . . . . . 33.2 Thread-Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43.3 Pipelining in Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53.4 Transmission Control Protocol . . . . . . . . . . . . . . . . . . . . . . . . . 6

4 Realisierung 74.1 Aufsetzen einer TCP Client-Server-Anwendung . . . . . . . . . . . . . . . 7

4.1.1 Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74.1.2 Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

4.2 Übertragen von Daten mit variabler Länge . . . . . . . . . . . . . . . . . 94.2.1 Empfängerseite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104.2.2 Senderseite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

4.3 Überwachen mehrerer Dateideskriptoren . . . . . . . . . . . . . . . . . . . 114.4 Auslagern der Netzwerkaufgaben . . . . . . . . . . . . . . . . . . . . . . . 124.5 Beispielhafter Programmablauf . . . . . . . . . . . . . . . . . . . . . . . . 134.6 Thread Kommunikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

4.6.1 Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144.6.2 Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4.7 Zuordnung von Nachrichten . . . . . . . . . . . . . . . . . . . . . . . . . . 174.8 Auswahl der Kommunikationsart . . . . . . . . . . . . . . . . . . . . . . . 184.9 Robustheit durch Heartbeating . . . . . . . . . . . . . . . . . . . . . . . . 19

5 Zusammenfassung und Ausblick 205.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205.2 Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

5.2.1 Multi-Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205.2.2 Parallele Anfragenbearbeitung . . . . . . . . . . . . . . . . . . . . 205.2.3 Erneuter Verbindungsaufbau nach Verbindunsgsverlust . . . . . 20

Literatur 21

i

Page 8: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul
Page 9: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

Abbildungsverzeichnis3.1 Arten der Client-Server-Kommunikation . . . . . . . . . . . . . . . . . . . 33.2 Piping zwischen zwei Threads . . . . . . . . . . . . . . . . . . . . . . . . . 53.3 TCP Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

4.1 Client-Server-Kommunikation in C . . . . . . . . . . . . . . . . . . . . . . 84.2 Datenstruktur zum Senden von Daten variabler Länge . . . . . . . . . . 94.3 Auslagerung der Netzwerkaufgaben . . . . . . . . . . . . . . . . . . . . . . 134.4 Threadinterner Kommunikationsablauf auf Serverseite . . . . . . . . . . 164.5 Finale Datenstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

iii

Page 10: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul
Page 11: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

1 MotivationIm Peter Grünberg Institut und Jülich Centre for Neutron Science ist das soge-nannte GR-Framework entwickelt worden, welches es ermöglicht, plattformunabhän-gige Visualisierungsanwendungen auszuführen. Bei der interaktiven Nutzung des GR-Frameworks aus Interpretersprachen, ist es sinnvoll, Benutzer-Interaktion und Visua-lisierung in verschiedene Prozesse zu zerlegen, da auf vielen Systemen sowohl Interpre-ter als auch Anzeigefenster im Haupt-Thread ausgeführt werden müssen. Die jewei-ligen Haupt-Threads liegen üblicherweise auf demselben Rechner, jedoch existierenAnwendungsfälle bei denen die Bearbeitung der Benutzereingaben auf einem exter-nen Rechner stattfindet, sodass beide Threads über das Netzwerk kommunizieren.Da das GR-Framework nicht über eine einheitliche, zuverlässige Möglichkeit verfügt,Kommunikation zwischen den Haupt-Threads von Anfragesteller und Anfragebear-beiter unabhängig von deren Ausführungsumgebung zu ermöglichen, ist es sinnvoll,eine Bibliothek zu entwickeln, die die Interprozesskommunikation für den Benutzerabstrahiert und geeignete Schnittstellen zur Verfügung stellt. Diese Seminararbeitbefasst sich mit der schrittweisen Entstehung der Bibliothek.

1

Page 12: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

2 AnforderungenDie während dieser Seminararbeit entstehende Bibliothek soll die Kommunikationzwischen einer Client- und einer Server-Anwendung auf Grundlage des Request-ResponseMusters ermöglichen. Hierzu ist es notwendig, eine Punkt-zu-Punkt-Verbindung zwi-schen Server und Client aufzubauen, über die Daten geschickt werden können. DerClient kann entscheiden, ob er synchron oder asynchron kommunizieren möchte undschickt Anfragen an den Server. Der Server empängt die Anfragen und bearbeitetdiese nacheinander. Nach der Bearbeitung werden die Antworten des Servers auf dieeinzelnen Anfragen wieder zurück an den Client gesendet. Zudem sollen sowohl Server-als auch Client-Anwendung stabil auf Verbindungsabbrüche reagieren und diese be-handeln. Da die entstehende Bibliothek in das GR-Framework integriert werden soll,wird die Programmiersprache C verwendet.

Zur Umsetzung einer solchen Bibliothek werden die folgende Teilschritte betrachtet:

1. Aufbau eine Verbindung zwischen Client- und Server-Anwendung

2. Versenden von beliebig langen Nachrichten über diese

3. Synchrones und asynchrones Kommunizieren

4. Erkennung und Behandlung von Verbindungsabbrüchen

2

Page 13: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

3 Grundlagen

3.1 Synchrone/Asynchrone KommunikationSynchrone KommunikationSynchrone Kommunikation zwischen Server und Client stellt eine Art der Kommuni-kation dar, bei der der Client einzelne Anfragen an den Server schickt und solangewartet, bis der Server diese beantwortet. In der Zeit zwischen Anfrage und dem Erhal-ten der Antwort befindet sich der Client in einem blockierenden Zustand. Der Serverbefindet sich zwischen dem Erhalten von Anfragen ebenfalls in einem blockierendenZustand und wird geweckt, sobald eine zu bearbeitende Anfrage eingetroffen ist. Diesewird dann unmittelbar bearbeitet.[MBW10, S.318]

Asynchrone KommunikationUnter asynchroner Kommunikation wird eine Art der Kommunikation verstanden,bei der das Senden und Empfangen von Daten zeitlich versetzt und ohne Blockie-ren des Clients durch Warten auf die Antwort des Servers stattfindet. Dies hat zurFolge, dass der Client mehrere Anfragen hintereinander schicken kann, ohne auf eineAntwort zu warten. Wenn der Server eine Anfrage fertig bearbeitet hat, schickt erdie Antwort zurück an den Client. Dieser wird benachrichtigt, sobald eine Antwortverfügbar ist und kann diese dann zu einem selbstbestimmten Zeitpunkt verwerten.[MBW10, S.319]

ServerClient

Zeit

Request

Response

(a) Synchron

ServerClient

Zeit

(b) Asynchron

Abbildung 3.1: Arten der Client-Server-Kommunikation

3

Page 14: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

3 Grundlagen

3.2 Thread-ProgrammierungThreads bieten eine Möglichkeit, Programme zu parallelisieren. So ist es möglich, einProgramm in mehrere Threads zu unterteilen, welche auf unterschiedlichen Prozessor-kernen ausgeführt werden können. Dies hat zum einen den Vorteil, dass rechenintensi-ve Aufgaben schneller bearbeitet werden können, da oftmals mehrere Prozessorkernean ihrer Bearbeitung beteiligt sind. Zum anderen können blockierende Aufgaben, alsoAufgaben, bei denen das Programm nicht weiter arbeiten kann bis die Aufgabe er-ledigt ist, in einen anderen Thread ausgelagert werden, sodass das Hauptprogrammweiter arbeiten kann. Zudem ist die parallele Bearbeitung mehrer Aufgaben möglich.Beispiel: Ein Programm soll die Summe der ersten n natürlichen Zahlen von 1 bis

n = 10000 berechnen. Die primitive Lösung wäre, diese in einer Schleife von 1 bis n zuberechnen. Diese Lösung ist auf Systemen mit mehreren Prozessorkernen allerdingslangsamer als möglich, da nur ein Prozessorkern zur Berechnung der Summe genutztwird und die anderen nicht beteiligt sind. Zudem blockiert das Programm, es ist alsonicht in der Lage weitere Aufgaben zu bearbeiten, solange die Summe nicht berechnetwurde. Wie oben bereits erwähnt, gibt es Möglickeiten, durch Threads Verbesserun-gen zu erzielen. Indem die Aufgabe auf mehrere Threads und damit, falls vorhanden,mehrere Prozessorkerne aufgeteilt wird, ist es möglich, die Berechnung stark zu ver-kürzen. Um Blockieren des Hauptthreads zu verhindern, damit dieser sich anderenAufgaben widmen kann, ist es möglich die gesamte Berechnung in andere Threadsauszulagern. Damit das Ergebnis der Summe wieder im Hauptthread verwendet wer-den kann, gibt es zwei Möglichkeiten. Entweder wartet der Hauptthread, sobald er dieSumme braucht aktiv, also blockierend, bis diese berechnet wurde. Alternativ kannder berechnende Thread den Hauptthread beispielsweise über Pipelining benachrich-tigen, dass die Summe fertig berechnet wurde, sodass dieser auf das Ergebnis zu einemselbstbestimmten Zeitpunkt zugreifen kann.Der Zugriff auf die von einem anderen Thread verwendeten Daten kann über globale

Variabeln geschehen. Da Threads im selben Adressraum liegen, haben sie Zugriff aufdenselben Speicherbereich, also auch den der anderen Threads. Um zu verhindern,dass Threads gleichzeitig auf dieselben Daten zugreifen und dadurch inkonsistenteWerte entstehen, werden üblicherweise Synchronisationsmaßnahmen ergriffen. Wennalso ein Thread auf Daten zugreifen will, schützt er diese während seines Zugriffes vorZugriffen durch andere Threads. [RR12, S. 279]

4

Page 15: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

3.3 Pipelining in Threads

3.3 Pipelining in ThreadsPipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver-wendet. Die nötigen Funktionen, um diese in C nutzen zu können, stellt das Modul<unistd.h> bereit. Der Inhalt einer Pipeline fließt immer in eine Richtung. Sie bestehtaus zwei sogenannten Dateideskriptoren, die jeweils ein Ende der Pipeline darstellen.In das eine Ende kann nur geschrieben, aus dem anderen kann nur gelesen werden.Diese werden mit dem folgenden Code erzeugt:

Listing 3.1: Erzeugen zweier Pipelines/* Erzeugen eines Integer Arrays , das beide Enden einerPipeline enthalten kann */int fd [2];/* Erzeugen einer Pipeline . */pipe ( fd );/* Auf fd [1] kann nun mit write () und auf fd [0] mit read ()zugegriffen werden */

Die nun entstande Pipeline kann jetzt von zwei Threads verwendet werden, um sichgegenseitig über diese Daten zu schicken. Mittels des Funktionsaufrufeswrite(fd[1], &ch, 1) kann ein einzelner char Wert ch in die Pipeline geschriebenwerden. Mit read(fd[0], &ch, 1), wird der erste char Wert in der Pipeline wiederausgelesen. Abbildung 3.2 veranschaulicht die Nutzung der nun entstandenen Pipelinedurch zwei Threads.Da Threads jedoch, wie bereits erwähnt, über denselben Speicherbereich verfügen, istes nicht üblich große Datenmengen durch Pipelines zu schicken. Sie werden primärgenutzt, um dem jeweils anderen Thread mitzuteilen, dass für diesen Daten bereitliegen, auf die er nun zugreifen kann. Hierzu reicht es einzelne Bytes über die Pipelinezu übertragen, um dem anderen Thread mitzuteilen, dass eine Berechnung fertig ist.[Wol06, S. 284]

Thread 1 Thread 2

fd[1]

fd[0]

Pipeline

Abbildung 3.2: Piping zwischen zwei Threads [Wol06, S. 284]

5

Page 16: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

3 Grundlagen

3.4 Transmission Control ProtocolZur Kommunikation zwischen Server und Client wird das Transmission Control Pro-tocol genutzt. Bei TCP handelt es sich um ein Netzwerk-Protokoll, welches dazuverwendet wird, Daten über eine Punkt-zu-Punkt Verbindung zwischen zwei Kom-munikationspartnern zu verschicken. Die zu verschickenden Daten werden in kleine,logisch zusammenhängende Pakete zerteilt und mit einem TCP-Header versehen. DerTCP-Header enthält Informationen über die Position, die das Paket innerhalb derGesamtmenge aller Pakete eines Datensatzes einnimmt, damit die Daten nach demVersenden wieder zusammengesetzt werden können. Ebenfalls enthält der Header dieAdresse des Empfängers, in Form der Portnummer, an die die Daten geschickt werdensollen. Dann werden sie an die IP-Adresse des Empfängers gesendet.Kommunikation mit TCP ist verbindungsorientiert und zuverlässig. Das bedeutet,

TCP stellt sicher, dass alle verschickten Daten beim Empfänger ankommen, andern-falls werden sie noch einmal geschickt. Dies ist im Hinblick auf die Seminararbeitwichtig, da oft große Daten wie beispielsweise Bilder zwischen Client und Server ver-schickt werden, bei denen eine vollständige Übertragung notwendig ist.Abbildung 3.3 zeigt einen TCP-Header, mit dem Daten, die über TCP versendet wer-den, versehen werden. [Jar13, S. 17]

Source Port Destination Port

Sequence Number

Acknowledgement Number

DataOffset

Re-served

Flags Window Size

Checksum Urgent Pointer

Options Padding

Abbildung 3.3: TCP Header

6

Page 17: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4 Realisierung

4.1 Aufsetzen einer TCP Client-Server-AnwendungUm Daten zwischen zwei Anwendungen verschicken zu können, wird zunächst eineTCP Verbindung zwichen einer Client- und einer Server-Anwendung in C erstellt.

4.1.1 ServerAuf Serverseite wird zuerst ein Socket erzeugt, über das er für Client-Anwendungenerreichbar ist. Anschließend wird ein struct serverAddr konfiguriert, in dem dieIP-Adressen, die der Server annehmen soll, und der Port, auf dem er erreichbar ist,sowie das verwendete Protokoll festgelegt werden. Als Protokoll wird hier IPv4 ver-wendet, da die Kommunikation über dass Netzwerk im PGI/JCNS standardmäßigüber IPv4 stattfindet. Nachdem die Adresse konfiguriert wurde, wird das Socket mit-tels der bind()-Funktion an diese gebunden. Mithilfe der listen()-Funktion lässtsich eine Warteschlange erstellen, in der sich offene Verbindungsanfragen von Client-Anwendungen aufhalten.Nun ist die Server-Anwendung bereit, ankommende Verbindungsanfragen anzu-

nehmen. Sobald die Wartengsschlange eingerichtet ist, können über die accept()-Funktion Verbindungswünsche aus dieser angenommen werden. Die accept()-Funktionliefert ein Socket, über das die Datenübertragung mit dem Client stattfinden kann.Listing 4.1 veranschaulicht die Erstellung der Serveranwendung.welcomeSocket = socket (AF_INET , SOCK_STREAM , 0);/* Anfordern eines neuen Sockets */serverAddr . sin_family = AF_INET ; /* Nutzen des IPv4 Protokolls */serverAddr . sin_port = htons( portNum );/* Setzen der Portnummer auf der der Server erreichbar ist */serverAddr . sin_addr . s_addr = inet_aton (" 0.0.0.0 ");/* auf allen IPv4 Interfaces warten */

bind( welcomeSocket , &serverAddr , sizeof ( serverAddr ));/* Binden des Sockets an seine Adresse */listen ( welcomeSocket , 5);/* Maximal 5 Verbindungsanfragen in der Warteschlange */

struct sockaddr * servaddr = ( struct sockaddr *)& serverAddr ;newSocket = accept ( welcomeSocket , servaddr , & addr_size );/* Accept liefert ein neues Socket zur Kommunikation mit Client */

Listing 4.1: Erstellen einer Server Anwendung in C

7

Page 18: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4 Realisierung

4.1.2 ClientDer Client erstellt ebenfalls ein Socket, über das er sich mit dem Server verbin-det. Nachdem die Ziel IP-Adresse des Servers, dessen Portnummer und das ver-wendete Protokoll in einem struct serverAddr gespeichert wurden, kann über dieconnect()-Funktion eine Verbindung zwischen Server und Client hergestellt werden.War der Verbindungsaufbau erfolgreich, können nun Daten zwischen Server und Cli-ent mittels der Funktionen send() und recv() ausgetauscht werden.clientSocket = socket (AF_INET , SOCK_STREAM , 0);/* Anfordern eines neuen Sockets */serverAddr . sin_family = AF_INET ; /* Nutzen des IPv4 Protokolls */serverAddr . sin_port = portNum ;/* Port auf dem der Server zu erreichen ist */serverAddr . sin_addr . s_addr = inet_aton (" 127.0.0.1 ");/*IP - Adresse des Servers , in diesem Fall localhost */

addr_size = sizeof ( serverAddr );

struct sockaddr * servaddr = ( struct sockaddr *) & serverAddrconnect ( clientSocket , servaddr , addr_size );/* Verbinden des clientSockets mit der erstellten Adresse serverAddr */

Listing 4.2: Erstellen einer Client Anwendung in C

Abbildung 4.1 verdeutlicht die einzelnen Schritte, die auf Sever und Client Seite not-wendig sind, bis Daten ausgetauscht werden können, grafisch.

Client

Socket

setze Serveraddr

Connect

Send/Recv

Server

Socket

Setze Adresse

Bind

Listen

Accept

Send/Recv

Verbinden

Datenaustausch

Abbildung 4.1: Client-Server-Kommunikation in C

Die nun erstellte Client-Server-Anwendung dient als Grundlage, um Daten zwi-schen Client und Server zu verschicken und wird im Folgenden erweitert und denAnforderungen gerecht modifiziert.

8

Page 19: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4.2 Übertragen von Daten mit variabler Länge

4.2 Übertragen von Daten mit variabler LängeDa zwischen Server und Client oft große Datenmengen wie beispielsweise Bilder ver-schickt werden, muss zunächst gewährleistet werden, dass Nachrichten mit variablerLänge, übertragen und empfangen werden können.Über <socket.h> werden zum Senden und Empfangen von Daten die Funktionensend(socket, *to_send, length, flags) undrecv(socket, *buffer, length, flags) bereitgestellt. Diese geben bei einem er-folgreichen Funktionsaufruf die Länge der gesendeten bzw. empfangenen Daten inByte zurück. Wie viel bei einem Funktionsaufruf empfangen bzw. gesendet werdenkann ist variabel und hängt von mehreren Faktoren ab. Daher muss sichergestelltwerden, dass die entsprechende Funktion so oft aufgerufen wird, bis die gesamtenDaten gesendet bzw. empfangen wurden.Da die Funktion recv() die Adresse eines Speicherbereiches erhält, in den sie die zuempfangenen Daten schreiben soll, ist es nötig, die Größe der Daten zu kennen, umgenug Speicherplatz für diese dynamisch zu allokieren. Aus diesem Grund wird vor derÜbertragung der eigentlichen Daten, deren Größe in Form eines unsigned int Wertesübertragen. Der unsigned int Datentyp bietet den Vorteil, auf nahezu allen Syste-men eine Länge von 4 Byte zu haben, sodass die Empfängerseite weiß, welchen Teilder empfangenen Daten sie als Länge und welchen als eigentlichen Datensatz zu Inter-pretieren hat. Bereits zur Kompilierzeit wird mittels eines static asserts überprüft, obder unsigned int Datentyp des Systems, auf dem die Server oder Client-Anwendungausgeführt werden soll, aus 4 Byte besteht. Andernfalls lässt sich die Anwendung nichtkompilieren. Bei dem static assert handelt es sich um ein switch-case, bei dem dererste Fall 0 ist und der zweite, je nach System, auf dem das Programm kompiliertwird, entweder 0 oder nicht. Wenn er 0 ist, lässt sich das Programm nicht kompilieren,da eine Switch-Anweisung mit zwei identischen Fällen ungültig ist.Da es möglich ist, dass Daten auf dem System, auf dem der Server läuft, anders ge-speichert werden als auf dem System des Clients (Little bzw. Big-Endian Systeme),ist es nötig, die Länge in Netzwerk-Byte-Reihenfolge, standardgemäß Big-Endian, zuverschicken. Nach der Übertragung werden sie gegebenenfalls wieder in die System-Speicherweise überführt.

Länge (unsigned int 8 Byte)

Daten

Abbildung 4.2: Datenstruktur zum Senden von Daten variabler Länge

9

Page 20: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4 Realisierung

4.2.1 EmpfängerseiteAuf der Empfängerseite werden die ersten 8 ankommenden Byte alsunsigned int size interpretiert und von der Netzwerk-Byte-Reihenfolge wieder aufdie Byte-Reihenfolge des jeweiligen Systems angepasst. Anschließend wird über diemalloc()-Funktion Speicherplatz mit der Größe der zu empfangenen Daten allokiert.In diesen Speicherbereich werden solange Daten, die über die recv-Funktion emp-fangen werden, geschrieben, bis die Anzahl der empfangenen Bytes gleich der vorherempfangenen Länge ist.

unsigned int receive_data (int socket , void* buffer ) {while (recvd < size_ul ){

/* empfangen der ersten 8 Byte als Laenge */recvd += recv(socket , (( char *)& size+recvd),size_ul -recvd , 0);/* speichern der Laenge in size */

}

size = fntohll (size );/* Netzwerk - Reihenfolge zu System - Reihenfolge */buffer = (char *) malloc (size );/* Allokieren des Speichers */recvd = 0;

while (recvd < size ){recvd += recv(socket , buffer +recvd , size -recvd , 0);/* Empfangen und Schreiben der Daten in den reservierten *//* Speicherbereich bis zur , zuerst empfangenen Laenge */

}

return size_ul +size; /* Wie viele Byte wurden empfangen */}

Listing 4.3: Empfangen von Nachrichten beliebiger Länge

4.2.2 SenderseiteAuf der Senderseite werden die Daten äquivalent verschickt. Die selbstgeschriebe-ne Funktion send_data(socket, data, length) empfängt das Socket, auf das ge-schrieben werden soll und die zu verschickenden Daten. Diese werden zusammen mitihrer Größe (in Netzwerk-Byte-Reihenfolge) über die recv()-Funktion auf die Emp-fängerseite geschickt.

10

Page 21: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4.3 Überwachen mehrerer Dateideskriptoren

4.3 Überwachen mehrerer DateideskriptorenDie nun erstellte Client-Server-Anwendung ist in der Lage, beliebig lange Nachrichtenzwischen Server und Client zu verschicken, allerdings nur synchron. Wenn der Clienteine Anfrage an den Server stellt, muss er danach durchgehend, also blockierend diereceive_data()-Funktion aufrufen, um die Antwort des Servers zu empfangen, da ernicht weiß, wann diese eintrifft. Dies hat den Nachteil, dass der Client in dieser Zeitnicht in der Lage ist, andere Aktivitäten, wie beispielsweise eine weitere Anfrage, dieverschickt werden soll, warzunehmen, da er ja blockiert.Mithilfe der select()-Funktion, können mehrere Dateideskriptoren gleichzeitig über-wacht werden. Diese erwartet als Parameter die Anzahl der zu überwachenden Da-teideskriptoren, sowie eine Menge fd_set dieser. Mit ihr kann der Client nach demVerschicken einer Anfrage, zeitgleich auf deren Antwort, als auch auf eine weitere,zu verschickende Anfrage warten. Wenn sie aufgerufen wird, blockiert die aufrufendeAnwendung solange, bis in einen der überwachten Dateideskriptoren etwas geschrie-ben wurde. In Listing 4.4 wird das Überwachen eines Verbindungssockets mit derselect()-Funktion demonstriert.FD_ZERO (& readfds );/* Leere Menge von Filedeskriptoren */

FD_SET (newSocket , & readfds );/* Socket ueber das mit Client kommuniziert wird , wird hinzugefuegt */

max_fd = newSocket ;/* integer Wert des Sockets von select erwartet */

select ( max_fd +1, &readfds , NULL , NULL , NULL );/* Aufruf der select Funktion *//* blockiert solange , bis Socket beschrieben wurde */

if ( FD_ISSET (newSocket , & readfds )) {/* bearbeite */}/* prueft ob newSocket beschrieben wurde */

Listing 4.4: Überwachen eines Verbindungssockets

Die nun entstandene Implementierung stellt weiterhin eine synchrone Kommunika-tion zwischen Server und Client dar. Nachdem eine TCP-Verbindung zwischen Serverund Client erstellt wurde, kann der Client über die send_data()-Funktion eine An-frage an den Server schicken. Der Server wird durch die select()-Funktion benach-richtigt und kann die Anfrage bearbeiten. Anschließend wird die Antwort zum Clientgeschickt. Dieser wird ebenfalls mittels select() benachrichtigt.Bei asynchroner Kommunikation stellt der Client hingegen unabhängig vom ServerAnfragen und arbeitet dann weiter, ohne aktiv auf deren Antwort zu warten. Um dieszu realisieren wird das, im nächsten Abschnitt verwendete Modell genutzt.

11

Page 22: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4 Realisierung

4.4 Auslagern der NetzwerkaufgabenBei asynchroner Kommunikation blockiert der Client nicht, nachdem er eine Anfragean den Server geschickt hat. Stattdessen soll er in der Lage sein, weitere Anfragen anden Server zu schicken, auch bevor die zuvor gestellten Anfragen beantwortet wurdenoder andere Aufgaben zu erledigen. Wenn er eine Antwort erhält, reicht es, wenn derClient über die eingetroffene Antwort informiert wird und diese dann, zu einem selbst-bestimmten Zeitpunkt auswertet. Auch der Server darf, wenn gerade keine Anfrageoffen ist nicht blockieren, sondern arbeitet unabhängig von eintreffenden Anfragen undwird lediglich über deren Eintreffen informiert. Um Client oder Server über eingetrof-fenen Daten zu benachrichtigen, wurde bereits die select()-Funktion vorgestellt.Da diese aber blockierend ist, reicht ihre Verwendung nicht aus, um asynchron zukommunizieren. Damit zeitgleich das Verbindungssocket auf eintreffende Daten über-wacht und gearbeitet werden kann, werden Client- und Server-Anwendung, mithilfevon Threads parallelisiert. Beide Anwendungen werden in zwei parallele Threads, denNetzwerk-Thread und den Haupt-Thread aufgeteilt. Der Netzwerk-Thread kümmertsich um die Netzwerkkommunikation, der Haupt-Thread arbeitet.Sofern keine neuen Daten aus dem Haupt-Thread ankommen, die über das Netz-

werk an die jeweils andere Anwendung verschickt werden müssen, oder Daten ausdem Netzwerk ankommen, die an den Haupt-Thread weitergeleitet werden müssen,befindet sich der Netzwerk-Thread in einem, durch die select()-Funktion realisier-ten, überwachenden Zustand, indem er auf Daten aus dem Netzwerk oder vom Haupt-Thread wartet. Wenn neue zu versendende Aufträge aus dem Haupt-Thread eintreffen,werden diese mithilfe der send_data()-Funktion in das Verbindungssocket geschrie-ben und der Thread kehrt wieder in den überwachenden Zustand zurück. Wenn derKommunikationspartner Daten in das Socket geschrieben hat, werden diese über diereceive_data()-Funktion empfangen und an den Hauptthread weitergeleitet undder Netzwerk-Thread kehrt ebenfalls in den überwachenden Zustand zurück.

12

Page 23: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4.5 Beispielhafter Programmablauf

4.5 Beispielhafter Programmablauf

Client ServerHaupt-Thread Netzwerk-Thread Netzwerk-Thread Haupt-Thread

ConnectAnfrage 1

Anfrage 1Anfrage 2

Anfrage 1Anfrage 2

Anfrage 2Antwort 1

Antwort 1Antwort 2

Antwort 1Antwort 2

Antwort 2Anfrage 3

Anfrage 3Anfrage 3Antwort 3

Antwort 3Antwort 3

Abbildung 4.3: Auslagerung der Netzwerkaufgaben

Alle Anfragen, die vom Client an den Server geschickt werden, sind nun asynchronund erwarten keine unmittelbare Antwort. Dies wird in Abbildung 4.3 verdeutlicht,in der ein möglicher Programmablauf nach der Parallelisierung von Server und Clientdargestellt ist. Anfrage 2 wird an den Server geschickt, bevor Anfrage 1 beantwortetwurde. Eine Anfrage durchläuft den Weg vom Client, über die jeweiligen Netzwerk-Threads, bis hin zum Server und wird dort bearbeitet. Anschließend wird sie dortbearbeitet und über das Netzwerk wieder zurück zum Client geschickt, wo sie vomNetzwerk-Thread an den Haupt-Thread weitergeleitet wird. Wie die threadinterneKommunikation stattfindet, wird in den nächsten Abschnitten beschrieben.

13

Page 24: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4 Realisierung

4.6 Thread Kommunikation4.6.1 PipelineAbbildung 4.3 veranschaulicht den logischen und zeitlichen Ablauf der Kommuni-kation zwischen den einzelnen Threads einer Client- und einer Server-Anwendung.Im Folgenden wird die interne Kommunikation zwischen den jeweiligen Haupt- undNetzwerk-Threads erläutert, also die Kommunikation innerhalb einer Anwendung.Wie bereits erwähnt, warten die jeweiligen Netzwerk-Threads zwischen den Aufgabenund werden durch die select()-Funktion über Daten, die über ein Socket ankommen,benachrichtigt. Die Kommunikation zwischen Haupt- und Netzwerk-Thread nutzt je-doch keine Sockets, da es einfachere Möglickeiten gibt, prozessintern zu kommuni-zieren, als eine TCP-Verbindung zu einem Thread aufzubauen, der Teil desselbenProzesses ist.Threads liegen im selben Adressraum und haben somit Zugriff auf denselben Speicher-

bereich. Durch diese Tatsache kann direkt auf Daten eines anderen Threads zugegrif-fen werden, ohne dass diese über ein Medium übertragen werden müssen. Hierzu mussdem Thread, der auf die Daten eines anderen Threads zugreift, jedoch erst einmalbekannt sein, dass die Daten auf die zugegriffen werden soll, bereit sind. Er mussalso benachrichtig werden. Dies kann mit sogennanten Pipelining zwischen Threadsrealisiert werden. Zwischen Haupt- und Netzwerk-Thread werden zwei Pipelines ge-nutzt, über die der jeweils andere Thread informiert werden kann, dass Daten fürdiesen bereit liegen. Da der Datenaustausch, wie bereits beschrieben, über den ge-meinsam genutzten Speicherbereich erfolgt, ist es nicht notwendig, andere Daten alskurze Mitteilungen über die Pipelines zu schicken. Für den Fall, dass Daten fertigbearbeitet wurden und auf diese zugegriffen werden kann, wird ein einzelnes Byte indie Pipeline geschrieben. Um diese zu überwachen kann ebenfalls, wie bei Sockets,die select()-Funktion genutzt werden. Diese erwartet eine Menge von zu überwa-chenden Dateideskriptoren, in die ebenfalls eine Pipeline hinzugefügt werden kann.Der Code in 4.5 zeigt, wie das Ende einer Pipeline fd[0] zusätzlich zu einem Socketmittels select() überwacht werden kann.FD_ZERO (& readfds );FD_SET ( clientSocket , & readfds ); /*zu ueberwachendes Socket */FD_SET (fd[0], & readfds ); /* Die Pipeline fd [0] wird hinzugeuegt */max_fd = fd [0];activity = select ( max_fd +1, &readfds , NULL , NULL , NULL );/* Das select gibt einen Wert zuruck , sobald einer derDeskriptoren beschrieben wurde */

if ( FD_ISSET ( clientSocket , & readfds )){ /* Wenn Socket beschrieben *//* empfange Daten und schicke sie an den Hauptthread */

}if ( FD_ISSET (fd[0], & readfds )){ /* Wenn Pipeline beschrieben */

/* hole Daten aus gemeinsamen Speicher und *//* schicke sie uber Socket */}

Listing 4.5: Pipelines mit select überwachen

14

Page 25: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4.6 Thread Kommunikation

4.6.2 QueueZu klären bleibt, wie die Threads den gemeinsamen Speicherbereich nutzen können.Dies ist über globale Variabeln möglich, auf die beide Threads Zugriff haben, dasie, wie bereits erwähnt, im selben Adressraum liegen. Da in der später laufendenAnwendung mehrere Anfragen auf eimmal an den Server gestellt werden können, istes sinnvoll, diese in in einer Datensturkur zu speichern, in der sie hintereinander liegenund abgearbeitet werden können. Wenn der Client beispielsweise fünf Anfragen ineinem kurzen zeitlichen Abstand stellt, kann der Server diese eventuell nicht schnellgenug beantworten. Die Anfragen müssen in einer Warteschlange bereit liegen, inder der Server sie auslesen und bearbeiten kann. Ebenso kann es vorkommen, dassder Server schneller Anfragen beantwortet, als der Netzwerk-Thread auf Serverseitediese Antworten wieder zurück an den Client schicken kann. Auch hier wird eineWarteschlange für die Antworten benötigt.Intern wird eine Queue verwendet. Eine Queue ist eine Datenstruktur, bei der Daten

nach dem First In First Out-Prinzip gespeichert werden. Möchte ein Thread Datenaus einer Queue entnehmen, verwendet er die Funktion queue_dequeue() und erhältden vordersten Eintrag, also die Daten, die sich am längsten in der Queue befinden.Sowohl auf Client, als auch auf Server-Seite, werden zwei Queues, jeweils eine für

eingehende und eine für ausgehende Daten, verwendet. Möchte der Client beispiels-weise zwei Anfragen an den Server schicken, werden diese an den Netzwerk-Threadübergeben, damit dieser die Daten mittels TCP an den Server schickt. Hierzu wirddie globale request_queue genutzt, an die, die zu verschickenden Daten nacheinan-der gehangen werden. Um den Netzwerk-Thread zu informieren, dass eine Anfragein der Queue verfügbar ist, die an den Server geschickt werden muss, schreibt derHaupt-Thread ein Byte in die Pipeline zwischen den Threads. Der Netzwerk-Thread,der mittels select()-Funktionsaufruf wartet, bemerkt, dass die Pipeline beschriebenwurde und holt sich mittels queue_dequeue() die Daten aus der request_queue, diedann an den Server verschickt werden.Bei der Nutzung von gemeinsamen Speicher, sind Synchronisationsmaßnahmen vor-

zunehmen. Andernfalls können Fehler auftreten, wenn beide Threads gleichzeitig aufeine der Queues zugreifen, einer lesend, einer schreibend. Aus diesem Grund wird eineQueue vor dem Zugreifen auf diese, durch einen Thread für Lese- und Schreibzugriffeanderer Threads gesperrt. Hierzu eignet sich ein sogenanntes Mutex. Mit der Funk-tion mutex_lock kann ein Thread eine Queue sperren, diese dann bearbeiten unddanach mit mutex_unlock wieder freigeben. So kann Fehlern aufgrund von gemein-sam genutzten Speicherbereich vorgebeugt werden. Wenn ein Thread auf eine derzeitgelockte Queue zugreifen will, muss er warten bis diese per mutex_unlock wiederfreigegeben wird. [Wol09, S. 8]

15

Page 26: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4 Realisierung

Netzwerk

Netzwerk Thread

Haupt Thread

Anfrage

Antwo

rt

Anfrage 1

Anfrage 2

Request

Queue

Antwort 1

Antwort 2Respo

nseQueue

Pipe

line

Pipe

line

Abbildung 4.4: Threadinterner Kommunikationsablauf auf Serverseite

Obenstehende Abbildung veranschaulicht den Arbeitsablauf seitens des Servers.Wenn die Anfrage eines Clients den Netzwerk-Thread des Servers erreicht, hängt die-ser die Anfrage in die globale, von beiden Threads genutzte, request_queue undbenachrichtigt diesen, indem ein Byte in die Pipeline geschrieben wird. Sobald derServer bereit ist, eine der Anfragen anzunehmen, informiert er sich über die Pipe-line, ob sich Anfragen in der Queue befinden. Falls ja, greift er auf die Anfrage zuund bearbeitet diese. Die Antwort wird mittels enqueue() an die response_queuegehangen. Abschliessend wird der Netzwerk-Thread über die Pipeline benachrichtigt,dass eine Antwort vorhanden ist, die an den Client zurückgeschickt wird.

16

Page 27: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4.7 Zuordnung von Nachrichten

4.7 Zuordnung von NachrichtenDa oft mehrere Anfragen gleichzeitig in Umlauf sind, muss es möglich sein, dieseeindeutig zu identifizieren, damit der Client weiß, auf welche Anfrage er nun eineAntwort erhalten hat. Aus diesem Grund wird jede Anfrage, die verschickt wird, miteiner eindeutigen Identifikationsnummer versehen, die beim Erstellen der Anfragegeneriert und beim Bearbeiten dieser auf Serverseite beibehalten wird. Wenn derClient nun seine Antwort erhält, kann er diese zur zugehörigen Anfrage zuordnen.Intern erzeugt der Client beim Erstellen der Anfragen eine Anfragenummer und sen-

det diese dann, zusammen mit der Anfrage selbst, durch die send_data()-Funktionzum Server. Hierzu wird diese um einen weiteren Parameter unsigned intrequest_number erweitert. Bei der Anfragenummer ist es nicht notwendig, sie vordem Übertragen in Netzwerk-Byte-Reihenfolge zu bringen, da diese bei der Bearbei-tung seitens des Servers nicht verändert wird. Um die nun entstandene Datenstrukturder Anfragen richtig empfangen zu können, wird auch die receive_data()-Funktionum einen weiteren Parameter erweitert unsigned int* request_number erweitert.Beim Aufruf dieser wird nun die Adresse eines unsigned int Wertes übergeben, diewährend des Empfangvorgangs mit der jeweiligen Anfragenummer beschrieben wird.Auf Serverseite wird die Anfrage durch den Netzwerk-Thread empfangen, welchersie durch die request_queue weiter an den Haupt-Thread leitet. Damit die Da-ten zusammen mit ihrer Nummer an die request queue gehängt werden können,werden sie vorher in einem struct number_data(siehe 4.6) zusammengefügt. Nach-dem die Anfrage im Hauptthread des Servers bearbeitet wurde, wird die Antwortan den Client in einem struct number_data zusammen mit der Anfragenummer indie response_queue gehangen, in der sie wieder an den Netzwerk-Thread zurück-geschickt werden. Dieser schreibt die Antwort in das Verbindungssocket. Der Clienterhält nun eine Antwort mit einer von ihm erzeugten Antwortnummer und kann dieseder zugehörigen von ihm gestellten, Anfrage zuordnen.struct number_data {

unsigned long number ; /* Anfragenummer */void* data; /* Anfrage */

};

Listing 4.6: einfaches Struct zur Übermittlung von Daten mit Anfragenummer

17

Page 28: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4 Realisierung

4.8 Auswahl der KommunikationsartMithilfe der bis hierher beschriebenen Client-Anwendung lassen sich beliebig vieleasynchrone Nachrichten an den Server schicken. Da die Auswahl der Art der Kom-munikation beim Anwender liegen soll, kann dieser beim Erzeugen einer Anfrage,auswählen, ob sie synchron oder asynchron verschickt wird. Wenn er sich für asyn-chrone Kommunikation entscheidet, wird die Anfrage im Hintergrund bearbeitet undder User wird lediglich benachrichtigt, sobald die zugehörige Antwort verfügbar ist.Bei einer synchronen Anfrage blockiert die Client Anwendung bis zum Erhalt.Das hierbei bestehende Problem liegt darin, dass es möglich ist, dass bereits asynchro-ne Anfragen verschickt wurden, die auf Beantwortung warten, bevor eine synchroneAnfrage geschickt wird. Da bisher, alle erhaltenen Antworten an die response_queuegehangen werden, kann es passieren, dass eine synchrone Antwort, auf die der Haupt-Thread wartet, hinter den asynchronen Antworten in der Queue liegt. Um an diesynchrone Antwort zu kommen, müsste der Haupt-Thread zuvor die asynchronenAntworten aus der Queue nehmen, wobei diese verloren gehen würden.Um dieses Problem intern zu lösen, werden synchrone Antworten vom Netzwerk-

Thread anders behandelt als asynchrone. Um eine synchrone Anfrage überhaupt vonanderen Anfragen unterscheidbar zu machen, wird für diese die Anfragenummer 0vorbehalten. Wenn der Netzwerk-Thread nun über das Netzwerk eine Antwort mitder Nummer 0 erhält, wird diese nicht über die response_queue an den Haupt-Threadweitergegeben. Statdessen wir ein globaler Zeiger

struct number_data* synch_answer verwendet, der auf die erhaltene synchroneAntwort zeigt. Der Haupt-Thread wird über das Erhalten einer Nachricht, durch einePipeline benachrichtigt. Je nach Art der Nachricht wird ein Byte mit unterschied-licher Belegung in die Pipeline geschrieben. Der Haupt-Thread befindet sich nachdem Versenden einer synchronen Nachricht, mithilfe von select() in einem überwa-chenden Zustand und wartet darauf, dass etwas in die Pipe geschrieben wird. Wennein Byte aus Einsen ankommt, erhöht er eine Variable, um später auf die erhaltenenasynchronen Antworten eingehen zu können. Wenn ein Byte aus Nullen ankommt,also die Antwort auf die der Haupt-Thread wartet, wird diese direkt verarbeitet.

18

Page 29: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

4.9 Robustheit durch Heartbeating

4.9 Robustheit durch HeartbeatingDa bei Kommunikation über das Netzwerk Probleme auftreten können, ist es not-wendig diese durch geeignete Fehlerbehandlung zu erkennen und zu behandeln, ohnedass das Programm unerwartet beendet wird. Ein häufig auftretendes Problem istder Verbindungsabbruch seitens des Clients oder Servers, bei dem diese nicht mehrantworten.Eine gängige Methode, um einen solchen Verbindungsabbruch zu detektieren, ist

das sogenannte Heartbeating. Hierbei teilen die Anwendungen ihrem Kommunikati-onspartner mit, dass sie noch existieren. Dies geschieht dabei über ein Heartbeat-Signal, welches in einer vorher bestimmten Zeit nach der letzten Interaktion zwi-schen den Anwendungen verschickt wird. Das Zeitintervall, nachdem eine Heartbeat-Nachricht verschickt wird, wird beim Starten des Servers, bzw. des Clients als Über-gabeparameter übergeben. Für gewöhnlich wird das Zeitintervall des Clients, kleinerals das des Servers gewählt, sodass der Client in der Regel dem Server mitteilt, dasser noch existiert und der Server als Reaktion ebenfalls eine Heartbeat-Nachricht ver-schickt. Wenn das Zeitinitervall nach der letzten Interaktion abgelaufen ist, schicktder Client eine Hearbeat-Nachricht an den Server und setzt eine interne Variable auftrue. Läuft die Zeit ein weiteres Mal ab, ohne dass der Server die Heartbeat Anfragebeantwortet hat, geht der Client von einem Verbindungsabbruch aus und schließt dasSocket. Andernfalls wird die interne Variable auf false gesetzt. Entsprechend giltdies für den Server, für den Fall, dass der Client nicht mehr ist. Das Zeitintervall wirdder select()-Funktion als struct timeval timeout übergeben.Um nun eine Heartbeat-Nachricht zu versenden, wird die send_data()-Funktion

mit dem Parameter char heartbeat = 1 aufgerufen. Durch diesen weiß die Funktion,dass sie lediglich eine Heartbeat-Nachricht verschicken soll. Eine Heartbeat-Nachrichtbesteht aus einem einfachen Byte.Die receive_data()-Funktion liest das erste empfangene Byte aus und beurteiltanhand diesem, was zu tun ist. Besteht das erste Byte aus acht Einsen, handelt essich um eine Heartbeat-Nachricht. Besteht es aus acht Nullen, dann handelt es sichum eine Anfrage, welche es zu empfangen gilt. Eine Heartbeat-Nachricht wird sofortvom Netwerk-Thread erwidert, eine Anfrage wie gewohnt bearbeitet.

Art der Nachricht (char 1 Byte)Länge(unsigned int 8 Byte)

Anfragenummer (unsigned int 8 Byte)

Daten

Abbildung 4.5: Finale Datenstruktur

19

Page 30: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

5 Zusammenfassung und Ausblick

5.1 ZusammenfassungMit der, in dieser Seminararbeit entstandenen Client-Server-Anwendung, ist es mög-lich, beliebige Binärdaten zwischen einer Client- und einer Server-Anwendung zu über-tragen. Die Kommunikation findet, unabhängig davon, ob Client und Server auf demgleichen Rechner liegen oder nicht, über TCP statt, wodurch in beiden Fällen einesichere Übertragung gesichert ist. Anfragen, die der Client an den Server schickt,können sowohl synchron als auch asynchron verschickt werden.

5.2 Ausblick5.2.1 Multi-ClientDie in der Seminararbeit erstellte Server-Client-Anwendung kommuniziert über einePunkt-zu-Punkt-Verbindung. Es kommuniziert also ein Server mit einem Client. Da esaber durchaus in der Praxis vorkommt, dass zeitgleich mehrere Clients mit dem Serverkommunizieren wollen, kann diese Funktionalität, mithilfe von mehreren Sockets (fürjeden Client ein eigenes Socket) ergänzt werden.

5.2.2 Parallele AnfragenbearbeitungEin weitere Verbesserungsmöglichkeit liegt in der Tatsache, dass Anfragen bishernacheinander abgearbeitet werden. So kann es vorkommen, dass Anfragen, die ei-gentlich eine kurze Bearbeitungszeit benötigen, trotzdem spät beantwortet werden,da sie nach Anfragen mit einer hohen Bearbeitungszeit liegen. Dieses Problem lässtsich beispielsweise mit einem Threadpool beheben, dem alle momentanen Anfragenzugeteilt werden, sodass diese parallel bearbeitet werden können.

5.2.3 Erneuter Verbindungsaufbau nach VerbindunsgsverlustDie Beispielanwendung erkennt Verbindungsabbrüche ohne, dass Client- oder Server-Anwendung blockieren. In vielen Fällen ist es sinnvoll, die Verbindung nach einemAbbruch neu aufzubauen und die Kommunikation fortzuführen. Zu diesem Zweckkann die Anwendung um ein geeignetes Sessionmanagement ergänzt werden.

20

Page 31: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul

Literatur[Jar13] D. Jarzyna. TCP/IP. mitp/bhv, 2013. isbn: 9783826695544.[MBW10] P. Mandl, A. Bakomenko und J. Weiss. Grundkurs Datenkommunikation:

TCP/IP-basierte Kommunikation: Grundlagen, Konzepte und Standards.Technische und Ingenieurinformatik. Vieweg+Teubner Verlag, 2010. isbn:9783834896995.

[RR12] T. Rauber und G. Rünger. Parallele Programmierung. eXamen.press.Springer Berlin Heidelberg, 2012. isbn: 9783642136047.

[Wol06] J. Wolf. Linux-UNIX-Programmierung: das umfassende Handbuch ; [Ein-stieg, Praxisbeispiele, Referenz ; System- und Netzwerkprogrammierung; X Window, GTK+, SDL, Werkzeuge, Sicherheit ; inkl. Openbooks zuC, UNIX und Debian]. Galileo computing. Galileo Press, 2006. isbn:9783898427494.

[Wol09] J. Wolf. C von A bis Z: das umfassende Handbuch. Galileo computing.Rheinwerk Verlag GmbH, 2009. isbn: 9783836214117.

21

Page 32: Nachrichtenbasierte Kommunikation zwischen ... · 3.3 Pipelining in Threads Pipelines werden, wie bereits erwähnt, zur Kommunikation zwischen Threads ver- wendet.DienötigenFunktionen,umdieseinCnutzenzukönnen,stelltdasModul