9. Programmierung TCP-IP
9.1. Allgemeines
9.1.1. Die Internetprotokolle
Hier geben wir eine Einführung in die Kommunikationsprotokolle des Internets, die auch als TCP/IP-Protokollsuite (Transfer Control Protocol / Internet Protocol) bezeichnet werden, benannt nach den beiden wichtigsten Protokollen. Es ist ratsam, dass der Leser ein umfassendes Verständnis der Funktionsweise von Netzwerken und insbesondere der Protokolle TCP/IP hat, bevor er sich mit der Entwicklung verteilter Anwendungen befasst.
Der folgende Text ist eine Teilübersetzung eines Textes aus dem Dokument „Lan Workplace for Dos – Administrator's Guide“ von NOVELL, einem Dokument aus den frühen 90er Jahren.
Das allgemeine Konzept zur Schaffung eines Netzwerks aus heterogenen Computern geht auf Forschungen der DARPA (Defense Advanced Research Projects Agency) in den Vereinigten Staaten zurück. Die DARPA entwickelte die unter den Bezeichnungen TCP/IP bekannte Protokollsuite, die es heterogenen Rechnern ermöglicht, miteinander zu kommunizieren. Diese Protokolle wurden in einem Netzwerk namens ARPAnet getestet, aus dem später das Netzwerk INTERNET hervorging. Die Protokolle TCP/IP definieren Formate sowie Regeln für die Übertragung und den Empfang, die unabhängig von der Netzwerkorganisation und der verwendeten Hardware sind.
Das durch das DARPA konzipierte und durch die Protokolle TCP/IP verwaltete Netzwerk ist ein paketvermitteltes Netzwerk. Ein solches Netzwerk überträgt Informationen über das Netzwerk in kleinen Einheiten, die als Pakete bezeichnet werden. Wenn also ein Computer eine große Datei überträgt, wird diese in kleine Teile zerlegt, die über das Netzwerk gesendet und am Zielort wieder zusammengesetzt werden. TCP/IP definiert das Format dieser Pakete, nämlich:
- Herkunft des Pakets
- Ziel
- Länge
- Typ
9.1.2. Das Muster OSI
Die Protokolle TCP/IP folgen in etwa dem offenen Netzwerkmodell namens OSI (Open Systems Interconnection Reference Model), das von der ISO (International Standards Organisation) definiert wurde. Dieses Modell beschreibt ein ideales Netzwerk, in dem die Kommunikation zwischen Rechnern durch ein siebenstufiges Modell dargestellt werden kann:
![]() |
Jede Schicht erhält Dienste von der darunterliegenden Schicht und stellt ihre eigenen Dienste der darüberliegenden Schicht zur Verfügung. Nehmen wir an, zwei Anwendungen auf den verschiedenen Rechnern A und B möchten miteinander kommunizieren: Dies geschieht auf der Ebene der Schicht Application. Sie müssen nicht alle Details der Funktionsweise des Netzwerks kennen: Jede Anwendung übergibt die Informationen, die sie übertragen möchte, an die darunterliegende Schicht, nämlich die Schicht Présentation. Die Anwendung muss daher nur die Schnittstellenregeln zur Schicht Présentation kennen.
Sobald sich die Informationen in der Schicht Présentation befinden, werden sie nach anderen Regeln an die Schicht Session weitergeleitet und so weiter, bis die Informationen auf dem physikalischen Medium ankommen und physikalisch an den Zielrechner übertragen werden. Dort durchläuft sie den umgekehrten Prozess, den sie auf dem Absenderrechner durchlaufen hat.
Auf jeder Schicht sendet der für den Versand der Informationen zuständige Senderprozess diese an einen Empfängerprozess auf dem anderen Rechner, der derselben Schicht angehört wie er selbst. Dies geschieht nach bestimmten Regeln, die als Protokoll der jeweiligen Schicht bezeichnet werden. Daraus ergibt sich folgendes endgültiges Kommunikationsschema:
![]() |
Die verschiedenen Schichten haben folgende Aufgaben:
Gewährleistet die Übertragung von Bits über ein physikalisches Medium. In dieser Schicht finden sich Endgeräte zur Datenverarbeitung (E.T.T.D.), wie z. B. Terminals oder Computer, sowie Geräte zum Abschluss von Datenverbindungen (E.T.C.D.), wie z. B. Modulatoren/Demodulatoren, Multiplexer und Konzentratoren. Die wichtigsten Punkte auf dieser Ebene sind: . die Wahl der Informationskodierung (analog oder digital) . die Wahl des Übertragungsmodus (synchron oder asynchron). | |
Versteckt die physikalischen Besonderheiten der physikalischen Schicht. Erkennt und korrigiert Übertragungsfehler. | |
Verwaltet den Weg, den die über das Netzwerk gesendeten Informationen nehmen müssen. Dies wird als routage bezeichnet: die Bestimmung der Route, die eine Information nehmen muss, damit sie ihren Empfänger erreicht. | |
Ermöglicht die Kommunikation zwischen zwei Anwendungen, während die vorherigen Schichten nur die Kommunikation zwischen Rechnern zuließen. Ein von dieser Schicht bereitgestellter Dienst kann das Multiplexing sein: Die Transportschicht kann dieselbe Netzwerkverbindung (von Rechner zu Rechner) nutzen, um Informationen mehrerer Anwendungen zu übertragen. | |
In dieser Schicht finden sich Dienste, die es einer Anwendung ermöglichen, eine Arbeitssitzung auf einem Remote-Rechner zu eröffnen und aufrechtzuerhalten. | |
Sie zielt darauf ab, die Darstellung der Daten auf den verschiedenen Rechnern zu vereinheitlichen. So werden Daten, die von Rechner A stammen, von der Schicht Présentation des Rechners A gemäß einem Standardformat „aufbereitet“, bevor sie über das Netzwerk gesendet werden. Sobald sie die Schicht Présentation des Zielrechners B erreichen, der sie anhand ihres Standardformats erkennt, werden sie auf andere Weise aufbereitet, damit die Anwendung des Rechners B sie erkennen kann. | |
Auf dieser Ebene befinden sich die Anwendungen, die in der Regel nah am Benutzer angesiedelt sind, wie beispielsweise E-Mail oder Dateiübertragung. |
9.1.3. Das Modell TCP/IP
Das Modell OSI ist ein ideales Modell, das bisher noch nicht realisiert wurde. Die Protokollsuite TCP/IP nähert sich diesem in folgender Form an:
![]() |
Physikalische Schicht
In lokalen Netzwerken kommt in der Regel entweder Ethernet- oder Token-Ring-Technologie zum Einsatz. Wir stellen hier nur die Ethernet-Technologie vor.
Ethernet
So wird eine paketvermittelte LAN-Technologie bezeichnet, die Anfang der 1970er Jahre bei PARC Xerox entwickelt und 1978 von Xerox, Intel und Digital Equipment standardisiert wurde. Das Netzwerk besteht physikalisch aus einem Koaxialkabel mit einem Durchmesser von etwa 1,27 cm und einer Länge von maximal 500 m. Es kann mithilfe von répéteurs erweitert werden, wobei zwei Geräte durch nicht mehr als zwei Repeater voneinander getrennt sein dürfen. Das Kabel ist passiv: Alle aktiven Komponenten befinden sich in den an das Kabel angeschlossenen Geräten. Jedes Gerät ist über eine Netzwerkzugangskarte mit dem Kabel verbunden, die Folgendes umfasst:
- einen Sender (transceiver), der das Vorhandensein von Signalen auf dem Kabel erkennt und analoge Signale in digitale umwandelt und umgekehrt.
- einen Koppler, der die digitalen Signale vom Sender empfängt und sie zur Verarbeitung an den Computer weiterleitet oder umgekehrt.
Die wichtigsten Merkmale der Ethernet-Technologie sind folgende:
- Übertragungsrate von 10 Megabit/Sekunde.
- Bus-Topologie: Alle Geräte sind an dasselbe Kabel angeschlossen
![]() |
- Broadcast-Netzwerk – Ein sendender Rechner überträgt Informationen über das Kabel zusammen mit der Adresse des Empfängerrechners. Alle angeschlossenen Rechner empfangen diese Informationen, doch nur derjenige, für den sie bestimmt sind, behält sie.
- Das Zugriffsverfahren läuft wie folgt ab: Der Sender, der eine Übertragung starten möchte, überwacht das Kabel – dabei erkennt er, ob eine Trägerwelle vorhanden ist oder nicht; ist dies der Fall, bedeutet dies, dass bereits eine Übertragung stattfindet. Dies ist die CSMA-Technik (Carrier Sense Multiple Access). Ist kein Träger vorhanden, kann ein Sender beschließen, seinerseits zu senden. Es können mehrere Sender diese Entscheidung treffen. Die gesendeten Signale vermischen sich: Man spricht von einer Kollision. Der Sender erkennt diese Situation: Während er auf das Kabel sendet, hört er gleichzeitig ab, was tatsächlich darauf übertragen wird. Stellt er fest, dass die auf dem Kabel übertragenen Informationen nicht mit den von ihm gesendeten übereinstimmen, schließt er daraus, dass eine Kollision vorliegt, und unterbricht die Übertragung. Die anderen Sender, die ebenfalls gesendet haben, verfahren ebenso. Jeder wird seine Übertragung nach einer zufälligen, vom jeweiligen Sender abhängigen Zeit wieder aufnehmen. Diese Technik wird als CD (Collision Detect) bezeichnet. Die Zugriffsmethode wird daher als CSMA/CD bezeichnet.
- Eine 48-Bit-Adressierung. Jeder Rechner verfügt über eine Adresse, die hier als physikalische Adresse bezeichnet wird und auf der Karte vermerkt ist, die ihn mit dem Kabel verbindet. Diese Adresse wird als Ethernet-Adresse des Rechners bezeichnet.
Netzwerkschicht
Auf dieser Schicht finden wir die Protokolle IP, ICMP, ARP und RARP.
Übermittelt Pakete zwischen zwei Knoten im Netzwerk | |
ICMP stellt die Kommunikation zwischen dem Protokollprogramm IP eines Rechners und dem eines anderen Rechners her. Es handelt sich also um ein Protokoll zum Austausch von Nachrichten innerhalb des Protokolls IP. | |
stellt die Zuordnung von der Internetadresse der Maschine zur physikalischen Adresse der Maschine her | |
stellt die Zuordnung von der physischen Adresse des Rechners zur Internetadresse des Rechners her |
Transport-/Sitzungsschichten
In dieser Schicht finden sich die folgenden Protokolle:
Gewährleistet eine zuverlässige Übermittlung von Informationen zwischen zwei Clients | |
Gewährleistet eine unzuverlässige Übermittlung von Informationen zwischen zwei Clients |
Anwendungs-/Präsentations-/Sitzungsschichten
Hier finden sich verschiedene Protokolle:
Terminalemulator, der es einem Rechner A ermöglicht, sich als Terminal mit einem Rechner B zu verbinden | |
ermöglicht Dateiübertragungen | |
ermöglicht Dateiübertragungen | |
ermöglicht den Austausch von Nachrichten zwischen Netzwerkbenutzern | |
wandelt einen Rechnernamen in die Internetadresse des Rechners um | |
Erstellt von sun MicroSystems, legt eine standardisierte, maschinenunabhängige Darstellung der Daten fest | |
ebenfalls von Sun definiert, ist ein transportschichtunabhängiges Kommunikationsprotokoll zwischen entfernten Anwendungen. Dieses Protokoll ist wichtig: Es entlastet den Programmierer von der Kenntnis der Details der Transportschicht und macht Anwendungen portabel. Dieses Protokoll basiert auf dem Protokoll XDR | |
das ebenfalls von Sun definiert wurde. Dieses Protokoll ermöglicht es einem Rechner, das Dateisystem eines anderen Rechners „zu sehen“. Es stützt sich auf das zuvor genannte Protokoll RPC |
9.1.4. Funktionsweise der Internetprotokolle
Anwendungen, die in der TCP/IP-Umgebung entwickelt wurden, nutzen in der Regel mehrere Protokolle dieser Umgebung. Ein Anwendungsprogramm kommuniziert mit der obersten Protokollschicht. Diese leitet die Informationen an die darunterliegende Schicht weiter und so weiter, bis sie das physikalische Medium erreichen. Dort werden die Informationen physikalisch an den Empfängercomputer übertragen, wo sie dieselben Schichten durchlaufen – diesmal in umgekehrter Reihenfolge –, bis sie die Empfängeranwendung der gesendeten Informationen erreichen. Das folgende Schema zeigt den Weg der Informationen:
![]() |
Nehmen wir ein Beispiel: die Anwendung FTP, die auf der Ebene der Schicht Application definiert ist und die Dateiübertragungen zwischen Rechnern ermöglicht.
- Die Anwendung liefert eine Bytefolge, die an die Schicht transport. übertragen werden soll
- Die Schicht transport unterteilt diese Bytefolge in segments und TCP und fügt am Anfang jedes Segments dessen Nummer hinzu. Die Segmente werden an die Netzwerkschicht weitergeleitet, die vom Protokoll IP gesteuert wird.
- Die Schicht IP erstellt ein Paket, das das empfangene Segment TCP einkapseln. Am Anfang dieses Pakets fügt sie die Internetadressen des Quell- und des Zielrechners ein. Außerdem ermittelt sie die physikalische Adresse des Zielrechners. Das Ganze wird an die Datenverbindungs- und physikalische Verbindungsebene weitergeleitet, d. h. an die Netzwerkkarte, die den Rechner mit dem physikalischen Netzwerk verbindet.
- Dort wird das Paket IP seinerseits in einen physikalischen Rahmen gekapselt und über das Kabel an den Empfänger gesendet.
- Auf dem Zielrechner führt die Schicht „Datenverbindung & Physikalische Verbindung“ den umgekehrten Vorgang durch: Sie entkapseln das Paket IP aus dem physikalischen Rahmen und leitet es an die Schicht IP weiter.
- Die Schicht IP überprüft, ob das Paket korrekt ist: Sie berechnet anhand der empfangenen Bits eine Prüfsumme (checksum), die im Header des Pakets wiederzufinden sein muss. Ist dies nicht der Fall, wird das Paket verworfen.
- Wird das Paket als korrekt befunden, entkapseln die Schicht IP das darin enthaltene Segment TCP und leitet es an die darüberliegende Schicht transport weiter.
- Die Schicht transport – in unserem Beispiel die Schicht TCP – überprüft die Segmentnummer, um die richtige Reihenfolge der Segmente wiederherzustellen.
- Außerdem berechnet sie eine Prüfsumme für das Segment TCP. Wird diese als korrekt befunden, sendet die Schicht TCP eine Empfangsbestätigung an den Quellrechner; andernfalls wird das Segment TCP abgelehnt.
- Nun muss die Schicht TCP nur noch den Datenteil des Segments an die Empfängeranwendung in der darüberliegenden Schicht weiterleiten.
9.1.5. Die Adressierung im Internet
Ein noeud in einem Netzwerk kann ein Computer, ein intelligenter Drucker, ein Dateiserver oder eigentlich alles sein, was mithilfe der Protokolle TCP/IP kommunizieren kann. Jeder Knoten verfügt über eine physikalische Adresse, deren Format vom Netzwerktyp abhängt. In einem Ethernet-Netzwerk wird die physikalische Adresse auf 6 Bytes kodiert. Eine Adresse in einem X25-Netzwerk ist eine 14-stellige Zahl.
Die Internetadresse eines Knotens ist eine logische Adresse: Sie ist unabhängig von der Hardware und dem verwendeten Netzwerk. Es handelt sich um eine 4-Byte-Adresse, die sowohl ein lokales Netzwerk als auch einen Knoten dieses Netzwerks identifiziert. Die Internetadresse wird üblicherweise in Form von vier Zahlen dargestellt, die die Werte der vier Bytes angeben und durch einen Punkt getrennt sind. So lautet die Adresse des Rechners „Lagaffe“ der naturwissenschaftlichen Fakultät in Angers 193.49.144.1 und die des Rechners „Liny“ 193.49.144.9. Daraus lässt sich ableiten, dass die Internetadresse des lokalen Netzwerks 193.49.144.0 lautet. In diesem Netzwerk können bis zu 254 Knoten vorhanden sein.
Da die Internetadressen oder IP-Adressen netzwerkunabhängig sind, kann ein Rechner aus Netzwerk A mit einem Rechner aus Netzwerk B kommunizieren, ohne sich Gedanken über die Art des Netzwerks zu machen, in dem er sich befindet: Es reicht aus, dass er dessen IP-Adresse kennt. Das Protokoll IP jedes Netzwerks übernimmt die Umwandlung zwischen IP-Adresse und physikalischer Adresse in beide Richtungen.
Die IP-Adressen müssen alle unterschiedlich sein. In Frankreich ist die INRIA für die Vergabe der IP-Adressen zuständig. Tatsächlich vergibt diese Stelle eine Adresse für Ihr lokales Netzwerk, zum Beispiel 193.49.144.0 für das Netzwerk der naturwissenschaftlichen Fakultät in Angers. Der Administrator dieses Netzwerks kann dann die Adressen von 193.49.144.1 bis 193.49.144.254 nach eigenem Ermessen zuweisen. Diese Adresse wird in der Regel in einer speziellen Datei auf jedem mit dem Netzwerk verbundenen Rechner gespeichert.
9.1.5.1. Die Adressklassen IP
Eine IP-Adresse ist eine Folge von 4 Bytes, die oft als I1.I2.I3.I4 bezeichnet wird und tatsächlich zwei Adressen enthält:
- die Netzwerkadresse
- die Adresse eines Knotens in diesem Netzwerk
Je nach Größe dieser beiden Felder werden die Adressen IP in drei Klassen unterteilt: Klasse A, B und C.
Klasse A
Die Adresse IP: I1.I2.I3.I4 hat die Form R1.N1.N2.N3, wobei
R1 | die Netzwerkadresse ist |
N1.N2.N3 | die Adresse eines Rechners in diesem Netzwerk ist |
Genauer gesagt hat eine Adresse der Klasse A in der Form IP folgende Struktur:
![]() |
Die Netzwerkadresse umfasst 7 Bit und die Knotenadresse 24 Bit. Somit sind 127 Netzwerke der Klasse A möglich, von denen jedes bis zu 2²⁴ Knoten umfassen kann.
Klasse B
Hier hat die Adresse IP: I1.I2.I3.I4 die Form R1.R2.N1.N2, wobei
R1.R2 | die Netzwerkadresse ist |
N1.N2 | die Adresse eines Rechners in diesem Netzwerk ist |
Genauer gesagt hat eine Adresse der Klasse B in der Form IP folgende Struktur:
![]() |
Sowohl die Netzwerkadresse als auch die Knotenadresse sind 2 Byte (genau 14 Bit) lang. Es können also 2¹⁴ Netzwerke der Klasse B mit jeweils bis zu 2¹⁶ Knoten vorhanden sein.
Klasse C
In dieser Klasse hat die Adresse IP: I1.I2.I3.I4 die Form R1.R2.R3.N1, wobei
R1.R2.R3 | die Netzwerkadresse ist |
N1 | die Adresse eines Rechners in diesem Netzwerk ist |
Genauer gesagt hat eine Adresse der Klasse C in der Form IP folgende Struktur:
![]() |
Die Netzwerkadresse umfasst 3 Bytes (minus 3 Bits) und die Knotenadresse 1 Byte. Somit sind 2²¹ Netzwerke der Klasse C mit jeweils bis zu 256 Knoten möglich.
Da die Adresse des Rechners Lagaffe der naturwissenschaftlichen Fakultät in Angers 193.49.144.1 lautet, sieht man, dass das höchstwertige Byte den Wert 193 hat, also im Binärsystem 11000001. Daraus lässt sich ableiten, dass es sich um ein Netzwerk der Klasse C handelt.
Reservierte Adressen
- Bestimmte Adressen wie IP sind Netzwerkadressen und keine Knotenadressen im Netzwerk. Dies sind diejenigen, bei denen die Knotenadresse auf 0 gesetzt ist. So ist die Adresse 193.49.144.0 die Netzwerkadresse IP der Fakultät für Naturwissenschaften in Angers. Folglich kann kein Knoten eines Netzwerks die Adresse Null haben.
- Wenn in einer Adresse wie IP die Knotenadresse ausschließlich aus Einsen besteht, handelt es sich um eine Broadcast-Adresse: Diese Adresse bezeichnet alle Knoten des Netzwerks.
- In einem Netzwerk der Klasse C, das theoretisch 2⁸ = 256 Knoten zulässt, bleiben nach Abzug der beiden unzulässigen Adressen nur noch 254 zulässige Adressen übrig.
9.1.5.2. Konvertierungsprotokolle Internetadresse <--> Physikalische Adresse
Wir haben gesehen, dass bei der Übertragung von Daten von einem Rechner zu einem anderen diese beim Durchlaufen der Schicht IP in Pakete gekapselt wurden. Diese haben folgende Form:
![]() |
Das Paket IP enthält also die Internetadressen des Quell- und des Zielrechners. Wenn dieses Paket an die Schicht weitergeleitet wird, die für die Übertragung über das physikalische Netzwerk zuständig ist, werden weitere Informationen hinzugefügt, um den physikalischen Rahmen zu bilden, der schließlich über das Netzwerk gesendet wird. Das Format eines Rahmens in einem Ethernet-Netzwerk sieht beispielsweise wie folgt aus:
![]() |
Im endgültigen Rahmen sind die physikalischen Adressen des Quell- und des Zielrechners enthalten. Wie werden diese ermittelt?
Da der Absender die Adresse IP des Rechners kennt, mit dem er kommunizieren möchte, ermittelt er dessen physikalische Adresse mithilfe eines speziellen Protokolls namens ARP (Address Resolution Protocol).
- Er sendet ein Paket eines speziellen Typs, das als ARP-Paket bezeichnet wird und die Adresse IP des Rechners enthält, dessen physikalische Adresse ermittelt werden soll. Außerdem hat er darauf geachtet, seine eigene Adresse IP sowie seine physikalische Adresse in das Paket aufzunehmen.
- Dieses Paket wird an alle Knoten im Netzwerk gesendet.
- Diese erkennen den besonderen Charakter des Pakets. Der Knoten, der seine Adresse IP im Paket erkennt, antwortet, indem er dem Absender des Pakets seine physikalische Adresse sendet. Wie ist das möglich? Er hat im Paket die Adressen IP und die physikalische Adresse des Absenders gefunden.
- Der Absender erhält somit die gesuchte physikalische Adresse. Er speichert sie im Speicher, um sie später verwenden zu können, falls weitere Pakete an denselben Empfänger gesendet werden sollen.
Die Adresse IP eines Rechners ist normalerweise in einer seiner Dateien gespeichert, sodass er diese einsehen kann, um sie zu erfahren. Diese Adresse kann geändert werden: Dazu muss lediglich die Datei bearbeitet werden. Die physikalische Adresse hingegen ist im Speicher der Netzwerkkarte hinterlegt und kann nicht geändert werden.
Wenn ein Administrator sein Netzwerk anders organisieren möchte, kann es erforderlich sein, die Adressen IP aller Knoten zu ändern und somit die verschiedenen Konfigurationsdateien der einzelnen Knoten zu bearbeiten. Dies kann mühsam sein und bei einer großen Anzahl von Rechnern zu Fehlern führen. Eine Methode besteht darin, den Rechnern keine IP-Adresse zuzuweisen: Man trägt dann einen speziellen Code in die Datei ein, in der der Rechner seine IP-Adresse finden sollte. Wenn der Rechner feststellt, dass er keine Adresse IP hat, fordert er diese über ein Protokoll namens RARP (Reverse Address Resolution Protocol) an. Sie sendet daraufhin über ein Netzwerk ein spezielles Paket namens RARP, analog zum vorherigen Paket ARP, in das sie ihre physikalische Adresse einträgt. Dieses Paket wird an alle Knoten gesendet, die es dann als Paket RARP erkennen. Einer von ihnen, der als Server RARP bezeichnet wird, verfügt über eine Datei, die die Zuordnung physikalische Adresse <--> IP-Adresse aller Knoten enthält. Er antwortet daraufhin dem Absender des Pakets RARP und sendet ihm seine Adresse IP zurück. Ein Administrator, der sein Netzwerk neu konfigurieren möchte, muss daher lediglich die Zuordnungsdatei des Servers RARP bearbeiten. Dieser muss normalerweise über eine feste Adresse IP verfügen, die er kennen muss, ohne selbst das Protokoll RARP nutzen zu müssen.
9.1.6. Die sogenannte IP-Netzwerkschicht des Internets
Das Protokoll IP (Internet Protocol) definiert die Form, die die Pakete annehmen müssen, und die Art und Weise, wie sie beim Senden oder Empfangen behandelt werden müssen. Diese besondere Art von Paket wird als IP-Datagramm bezeichnet. Wir haben es bereits vorgestellt:
![]() |
Wichtig ist, dass das IP-Datagramm neben den zu übertragenden Daten auch die Internetadressen des Quell- und des Zielrechners enthält. So weiß der Empfängerrechner, von wem er eine Nachricht erhält.
Im Gegensatz zu einem Netzwerkrahmen, dessen Länge durch die physikalischen Eigenschaften des Netzwerks bestimmt wird, über das er übertragen wird, wird die Länge des Datagramms IP durch die Software festgelegt und ist daher in verschiedenen physikalischen Netzwerken gleich. Wir haben gesehen, dass das Datagramm IP beim Abstieg von der Netzwerkschicht in die physikalische Schicht in einen physikalischen Rahmen gekapselt wurde. Als Beispiel haben wir den physikalischen Rahmen eines Ethernet-Netzwerks angeführt:
![]() |
Die physikalischen Frames werden von Knoten zu Knoten zu ihrem Ziel weitergeleitet, das sich möglicherweise nicht im selben physikalischen Netzwerk wie der sendende Rechner befindet. Das Paket IP kann daher nacheinander in verschiedene physikalische Frames auf den Knoten gekapselt werden, die die Verbindung zwischen zwei Netzwerken unterschiedlichen Typs herstellen. Es kann auch vorkommen, dass das Paket IP zu groß ist, um in einen physikalischen Frame gekapselt zu werden. Die Software IP des Knotens, an dem dieses Problem auftritt, zerlegt das Paket IP dann nach genauen Regeln in fragments, wobei jedes dieser Teile anschließend über das physikalische Netzwerk gesendet wird. Sie werden erst an ihrem endgültigen Zielort wieder zusammengesetzt.
9.1.6.1. Das Routing
Das Routing ist die Methode, mit der die Pakete IP an ihren Bestimmungsort geleitet werden. Es gibt zwei Methoden: das direkte Routing und das indirekte Routing.
Direktes Routing
Direktes Routing bezeichnet die Weiterleitung eines Pakets IP direkt vom Absender zum Empfänger innerhalb desselben Netzwerks:
- Der Absender eines Datagramms IP verfügt über die Adresse IP des Empfängers.
- Sie ermittelt die physikalische Adresse des Empfängers über das Protokoll ARP oder aus ihren Tabellen, falls diese Adresse bereits vorliegt.
- Er sendet das Paket über das Netzwerk an diese physikalische Adresse.
Indirektes Routing
Indirektes Routing bezeichnet die Weiterleitung eines Pakets IP an ein Ziel, das sich in einem anderen Netzwerk befindet als dem, zu dem der Absender gehört. In diesem Fall unterscheiden sich die Netzwerkadressanteile der Adressen IP des Quell- und des Zielrechners. Der Quellrechner erkennt diesen Umstand. Er sendet das Paket dann an einen speziellen Knoten, den sogenannten Router (router), der ein lokales Netzwerk mit anderen Netzwerken verbindet und dessen Adresse IP er in seinen Tabellen findet – eine Adresse, die ursprünglich entweder aus einer Datei, aus einem permanenten Speicher oder über im Netzwerk zirkulierende Informationen bezogen wurde.
Ein Router ist an zwei Netzwerke angeschlossen und verfügt innerhalb dieser beiden Netzwerke über eine Adresse IP.
![]() |
In unserem obigen Beispiel:
- Netzwerk Nr. 1 hat die Internetadresse 193.49.144.0 und Netzwerk Nr. 2 die Adresse 193.49.145.0.
- Innerhalb von Netzwerk Nr. 1 hat der Router die Adresse 193.49.144.6 und innerhalb von Netzwerk Nr. 2 die Adresse 193.49.145.3.
Die Aufgabe des Routers besteht darin, das empfangene Paket IP, das in einem für Netzwerk Nr. 1 typischen physikalischen Rahmen enthalten ist, in einen physikalischen Rahmen umzuwandeln, der im Netzwerk Nr. 2 übertragen werden kann. Befindet sich die Adresse IP des Paketempfängers im Netzwerk Nr. 2, sendet der Router das Paket direkt an ihn; andernfalls leitet er es an einen anderen Router weiter, der das Netzwerk Nr. 2 mit einem Netzwerk Nr. 3 verbindet, und so weiter.
9.1.6.2. Fehler- und Kontrollmeldungen
Ebenfalls in der Netzwerkschicht, also auf derselben Ebene wie das Protokoll IP, gibt es das Protokoll ICMP (Internet Control Message Protocol). Es dient dazu, Meldungen über den internen Betrieb des Netzwerks zu senden: ausgefallene Knoten, Überlastung an einem Router usw. Die ICMP-Meldungen werden in IP-Pakete gekapselt und über das Netzwerk gesendet. Die IP-Schichten der verschiedenen Knoten ergreifen entsprechend den empfangenen ICMP-Meldungen die geeigneten Maßnahmen. Somit bekommt eine Anwendung selbst diese netzwerkspezifischen Probleme nie mit. Ein Knoten nutzt die Informationen ICMP, um seine Routing-Tabellen zu aktualisieren.
9.1.7. Die Transportschicht: die Protokolle UDP und TCP
9.1.7.1. Das Protokoll UDP: User Datagram Protocol
Das Protokoll UDP ermöglicht einen unzuverlässigen Datenaustausch zwischen zwei Punkten, d. h., die korrekte Weiterleitung eines Pakets an seinen Bestimmungsort ist nicht garantiert. Die Anwendung kann dies, falls gewünscht, selbst handhaben, indem sie beispielsweise nach dem Senden einer Nachricht auf eine Empfangsbestätigung wartet, bevor sie die nächste sendet.
Bisher haben wir auf Netzwerkebene von IP-Adressen von Rechnern gesprochen. Auf einem Rechner können jedoch gleichzeitig verschiedene Prozesse laufen, die alle miteinander kommunizieren können. Daher muss beim Versenden einer Nachricht nicht nur die Adresse IP des Empfängersystems angegeben werden, sondern auch der „Name“ des Empfängerprozesses. Dieser Name ist eigentlich eine Nummer, die als Portnummer bezeichnet wird. Bestimmte Nummern sind für Standardanwendungen reserviert: Port 69 beispielsweise für die Anwendung TFTP (Trivial File Transfer Protocol). Die vom Protokoll UDP verwalteten Pakete werden auch als Datagramme bezeichnet. Sie haben folgende Form:
![]() |
Diese Datagramme werden in IP-Pakete und anschließend in physikalische Rahmen gekapselt.
9.1.7.2. Das Protokoll TCP: Transfer Control Protocol
Für eine sichere Kommunikation reicht das Protokoll UDP nicht aus: Der Anwendungsentwickler muss selbst ein Protokoll entwickeln, mit dem er die korrekte Weiterleitung der Pakete überprüfen kann.
Das Protokoll TCP (Transfer Control Protocol) vermeidet diese Probleme. Es weist folgende Merkmale auf:
- Der Prozess, der Daten senden möchte, baut zunächst eine Verbindung zu dem Prozess auf, der die zu sendenden Informationen empfangen soll. Diese Verbindung wird zwischen einem Port des sendenden Rechners und einem Port des empfangenden Rechners hergestellt. Zwischen den beiden Ports entsteht so ein virtueller Pfad, der ausschließlich den beiden Prozessen vorbehalten ist, die die Verbindung hergestellt haben.
- Alle vom Quellprozess gesendeten Pakete folgen diesem virtuellen Pfad und kommen in der Reihenfolge an, in der sie gesendet wurden – was beim Protokoll UDP nicht gewährleistet war, da die Pakete unterschiedliche Pfade nehmen konnten.
- Die gesendeten Informationen erscheinen als kontinuierlicher Strom. Der sendende Prozess sendet Informationen in seinem eigenen Rhythmus. Diese werden nicht unbedingt sofort gesendet: Das Protokoll TCP wartet, bis genügend Daten vorhanden sind, um sie zu versenden. Sie werden in einer Struktur namens Segment TCP gespeichert. Sobald dieses Segment gefüllt ist, wird es an die Schicht IP weitergeleitet, wo es in ein Paket IP gekapselt wird.
- Jedes vom Protokoll TCP gesendete Segment wird nummeriert. Das empfangende Protokoll TCP überprüft, ob es die Segmente in der richtigen Reihenfolge erhält. Für jedes korrekt empfangene Segment sendet es eine Empfangsbestätigung an den Absender.
- Sobald der Absender diese Bestätigung erhält, teilt er dies dem sendenden Prozess mit. Dieser kann somit feststellen, dass ein Segment erfolgreich angekommen ist, was mit dem Protokoll UDP nicht möglich war.
- Wenn das Protokoll TCP, das ein Segment gesendet hat, nach einer gewissen Zeit keine Empfangsbestätigung erhält, sendet es das betreffende Segment erneut und gewährleistet so die Qualität des Informationsübertragungsdienstes.
- Die zwischen den beiden kommunizierenden Prozessen hergestellte virtuelle Verbindung lautet full-duplex: Das bedeutet, dass Informationen in beide Richtungen übertragen werden können. So kann der Zielprozess Empfangsbestätigungen senden, während der Quellprozess weiterhin Informationen sendet. Dies ermöglicht es beispielsweise dem Quellprotokoll TCP, mehrere Segmente zu senden, ohne auf eine Empfangsbestätigung zu warten. Stellt es nach einer gewissen Zeit fest, dass es die Empfangsbestätigung für ein bestimmtes Segment mit der Nummer n nicht erhalten hat, setzt es die Übertragung der Segmente an dieser Stelle fort.
9.1.8. Die Anwendungsschicht
Über den Protokollen UDP und TCP gibt es verschiedene Standardprotokolle:
TELNET
Dieses Protokoll ermöglicht es einem Benutzer eines Rechners A im Netzwerk, eine Verbindung zu einem Rechner B (oft als Host-Rechner bezeichnet) herzustellen. TELNET emuliert auf dem Rechner A ein sogenanntes universelles Terminal. Der Benutzer verhält sich somit so, als hätte er ein an Rechner B angeschlossenes Terminal. Telnet basiert auf dem Protokoll TCP.
FTP: (File Transfer Protocol)
Dieses Protokoll ermöglicht den Austausch von Dateien zwischen zwei entfernten Rechnern sowie Dateivorgänge wie beispielsweise das Anlegen von Verzeichnissen. Es basiert auf dem Protokoll TCP.
TFTP: (Trivial File Transfer Control)
Dieses Protokoll ist eine Variante von FTP. Es basiert auf dem Protokoll UDP und ist weniger komplex als FTP.
DNS: (Domain Name System)
Wenn ein Benutzer Dateien mit einem Remote-Rechner austauschen möchte, beispielsweise über FTP, muss er die Internetadresse dieses Rechners kennen. Um beispielsweise FTP auf dem Rechner „Lagaffe“ der Universität Angers auszuführen, müsste man FTP wie folgt starten: FTP 193.49.144.1
Dazu ist ein Verzeichnis erforderlich, das die Zuordnung zwischen Rechner und Adresse IP enthält. Wahrscheinlich würden die Rechner in diesem Verzeichnis durch symbolische Namen wie die folgenden bezeichnet:
Rechner DPX2/320 der Universität Angers
Sun-Rechner der Universität Angers mit der Adresse ISERPA
Es liegt auf der Hand, dass es angenehmer wäre, einen Rechner mit einem Namen statt mit seiner Adresse IP zu bezeichnen. Damit stellt sich das Problem der Eindeutigkeit der Namen: Es gibt Millionen miteinander verbundener Rechner. Man könnte sich vorstellen, dass eine zentrale Stelle die Namen vergibt. Das wäre zweifellos recht aufwendig. Die Verwaltung der Namen wurde daher auf Domänen verteilt. Jede Domäne wird von einer in der Regel sehr schlanken Organisation verwaltet, die bei der Wahl der Rechnernamen völlige Freiheit genießt. So gehören die Rechner in Frankreich zur Domäne „fr“, die vom Inria in Paris verwaltet wird. Um die Dinge weiter zu vereinfachen, wird die Verwaltung noch weiter aufgeteilt: Innerhalb der Domäne „fr“ werden weitere Domänen angelegt. So gehört die Universität Angers zur Domäne „univ-Angers“. Die für diese Domäne zuständige Stelle hat völlige Freiheit bei der Benennung der Rechner im Netzwerk der Universität Angers. Bislang wurde diese Domäne noch nicht unterteilt. An einer großen Universität mit vielen vernetzten Rechnern könnte dies jedoch der Fall sein.
Die Maschine DPX2/320 der Universität Angers wurde in Lagaffe umbenannt, während ein PC und ein 486DX50 in liny umbenannt wurden. Wie lassen sich diese Rechner von außen referenzieren? Indem man die Hierarchie der Domänen angibt, zu denen sie gehören. Der vollständige Name des Rechners „Lagaffe“ lautet somit:
Lagaffe.univ-Angers.fr
Innerhalb der Domänen können relative Namen verwendet werden. Innerhalb der Domäne „fr“ und außerhalb der Domäne „univ-Angers“ kann der Rechner „Lagaffe“ also wie folgt referenziert werden:
Lagaffe.univ-Angers
Schließlich kann er innerhalb der Domäne „univ-Angers“ einfach mit
Lagaffe
Eine Anwendung kann einen Rechner also über seinen Namen referenzieren. Letztendlich muss man jedoch die Internetadresse dieses Rechners ermitteln. Wie wird das bewerkstelligt? Nehmen wir an, man möchte von einem Rechner A aus mit einem Rechner B kommunizieren.
- Wenn der Rechner B zur gleichen Domäne wie der Rechner A gehört, findet man seine Adresse IP wahrscheinlich in einer Datei auf dem Rechner A.
- Andernfalls findet Rechner A in einer anderen Datei oder derselben wie zuvor eine Liste mit einigen Nameservern und deren Adressen IP. Ein Nameserver ist dafür zuständig, die Zuordnung zwischen einem Rechnernamen und seiner Adresse IP herzustellen. Rechner A sendet eine spezielle Anfrage an den ersten Nameserver seiner Liste, die sogenannte DNS-Anfrage, die somit den Namen des gesuchten Rechners enthält. Wenn der abgefragte Server diesen Namen in seinen Tabellen hat, sendet er die entsprechende Adresse IP an Rechner A. Andernfalls findet der Server in seinen Dateien ebenfalls eine Liste von Nameservern, die er abfragen kann. Dies wird er dann tun. So werden eine bestimmte Anzahl von Nameservern abgefragt, jedoch nicht wahllos, sondern so, dass die Anzahl der Anfragen minimiert wird. Wird der Rechner schließlich gefunden, gelangt die Antwort zurück zum Rechner A.
XDR: (eXternal-Datendarstellung)
Dieses von Sun MicroSystems entwickelte Protokoll legt eine standardisierte, maschinenunabhängige Datendarstellung fest.
RPC: (Remote Procedure Call)
Ebenfalls von Sun definiert, handelt es sich hierbei um ein transportschichtunabhängiges Kommunikationsprotokoll zwischen entfernten Anwendungen. Dieses Protokoll ist wichtig: Es entlastet den Programmierer von der Kenntnis der Details der Transportschicht und macht Anwendungen portabel. Dieses Protokoll basiert auf dem Protokoll XDR
NFS: Network File System
Dieses ebenfalls von Sun definierte Protokoll ermöglicht es einem Rechner, das Dateisystem eines anderen Rechners „zu sehen“. Es basiert auf dem zuvor genannten Protokoll RPC.
9.1.9. Fazit
In dieser Einführung haben wir einige Grundzüge der Internetprotokolle vorgestellt. Um dieses Thema zu vertiefen, empfiehlt sich die Lektüre des ausgezeichneten Buches von Douglas Comer:
Titel | TCP/IP: Architektur, Protokolle, Anwendungen. |
Autor | Douglas COMER |
Verlag | InterEditions |
9.2. Verwaltung von Netzwerkadressen
Ein Rechner im Internet wird eindeutig durch eine IP-Adresse (Internet Protocol) der Form I1.I2.I3.I4 definiert, wobei In eine Zahl zwischen 1 und 254 ist. Er kann auch durch einen ebenfalls eindeutigen Namen definiert werden. Dieser Name ist nicht zwingend erforderlich, da Anwendungen letztendlich immer die IP-Adressen der Rechner verwenden. Sie dienen lediglich dazu, den Benutzern die Arbeit zu erleichtern. So ist es mit einem Browser einfacher, die Adresse URL http://www.ibm.com abzufragen als die Adresse URL http://129.42.17.99, obwohl beide Methoden möglich sind. Die Zuordnung zwischen den Adressen IP und nomMachine wird durch einen verteilten Internetdienst namens DNS (Domain Name System) gewährleistet. Die Plattform .NET bietet die Klasse „Dns“ zur Verwaltung von Internetadressen:

Die meisten Methoden der Klasse sind statisch. Sehen wir uns diejenigen an, die für uns von Interesse sind:
Gibt eine Adresse IPHostEntry ausgehend von einer Adresse IP in der Form „I1.I2.I3.I4“ zurück. Löst eine Ausnahme aus, wenn die Maschine address nicht gefunden werden kann. | |
Gibt anhand eines Rechnernamens eine Adresse IPHostEntry zurück. Löst eine Ausnahme aus, wenn der Rechner name nicht gefunden werden kann. | |
Gibt den Namen des Rechners zurück, auf dem das Programm ausgeführt wird, das diese Anweisung ausführt |
Netzwerkadressen vom Typ IPHostEntry haben folgende Form:
![]() |
Die Eigenschaften, die uns interessieren:
Liste der IP-Adressen eines Rechners. Während eine IP-Adresse einen und nur einen physischen Rechner bezeichnet, kann ein physischer Rechner mehrere IP-Adressen haben. Dies ist der Fall, wenn er über mehrere Netzwerkkarten verfügt, die ihn mit verschiedenen Netzwerken verbinden. | |
Liste der Aliase eines Rechners, der durch einen Hauptnamen und Aliase bezeichnet werden kann | |
der Name des Rechners, falls vorhanden |
Von der Klasse IPAddress behalten wir den Konstruktor sowie die folgenden Eigenschaften und Methoden im Gedächtnis:

Ein Objekt vom Typ [IPAddress] kann mit der Methode ToString() in die Zeichenkette I1.I2.I3.I4 umgewandelt werden. Umgekehrt kann man mit der statischen Methode IPAddress.Parse("I1.I2.I3.I4") aus einer Zeichenkette I1.I2.I3.I4 ein Objekt IPAddress erhalten. Betrachten wir das folgende Programm, das den Namen des Rechners anzeigt, auf dem es ausgeführt wird, und anschließend interaktiv die Zuordnungen zwischen der Adresse IP und dem Rechnernamen ausgibt:
dos>address1
Machine Locale=tahe
Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171
Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa
Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99
Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99
Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]
Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : fin
Das Programm sieht wie folgt aus:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
Imports System.Net
Imports System.Text.RegularExpressions
' Testmodul
Public Module adresses
Sub Main()
' zeigt den Namen des lokalen Rechners an
' und gibt anschließend interaktiv Informationen zu den Netzwerkrechnern aus
' die durch einen Namen oder eine Adresse identifiziert werden IP
' lokaler Rechner
Dim localHost As String = Dns.GetHostName()
Console.Out.WriteLine(("Machine Locale=" + localHost))
' interaktive Frage-Antwort-Runde
Dim machine As String
Dim adresseMachine As IPHostEntry
While True
' Eingabe des Namens des gesuchten Rechners
Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
machine = Console.In.ReadLine().Trim().ToLower()
' Fertig?
If machine = "fin" Then
Exit While
End If
' Adresse I1.I2.I3.I4 oder Rechnername?
Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
' Ausnahmebehandlung
Try
If isIPV4 Then
adresseMachine = Dns.GetHostByAddress(machine)
Else
adresseMachine = Dns.GetHostByName(machine)
End If
' der Name
Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
' die Adressen IP
Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
Dim i As Integer
For i = 1 To adresseMachine.AddressList.Length - 1
Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
Next i
Console.Out.WriteLine()
' Aliase
If adresseMachine.Aliases.Length <> 0 Then
Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
For i = 1 To adresseMachine.Aliases.Length - 1
Console.Out.Write(("," + adresseMachine.Aliases(i)))
Next i
Console.Out.WriteLine()
End If
Catch
' Der Rechner existiert nicht
Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
End Try
End While
End Sub
End Module
9.3. Programmierung TCP-IP
9.3.1. Allgemeines
Betrachten wir die Kommunikation zwischen zwei entfernten Rechnern A und B:
![]() |
Wenn eine Anwendung AppA auf Rechner A mit einer Anwendung AppB auf Rechner B im Internet kommunizieren möchte, muss sie mehrere Informationen kennen:
- die Adresse IP oder den Namen des Rechners B
- die Portnummer, über die die Anwendung AppB arbeitet. Denn der Rechner B kann zahlreiche Anwendungen unterstützen, die über das Internet arbeiten. Wenn er Informationen aus dem Netzwerk empfängt, muss er wissen, für welche Anwendung diese Informationen bestimmt sind. Die Anwendungen auf Rechner B haben über Schnittstellen, die auch als Kommunikationsports bezeichnet werden, Zugriff auf das Netzwerk. Diese Information ist in dem von Rechner B empfangenen Paket enthalten, damit es an die richtige Anwendung weitergeleitet werden kann.
- Die von Rechner B verstandenen Kommunikationsprotokolle. In unserer Untersuchung werden wir ausschließlich die Protokolle TCP-IP verwenden.
- Das von der Anwendung AppB akzeptierte Dialogprotokoll. Tatsächlich werden die Maschinen A und B miteinander „kommunizieren“. Was sie sich mitteilen, wird in den Protokollen TCP-IP gekapselt. Wenn jedoch am Ende der Kette die Anwendung AppB die von der Anwendung AppA gesendete Information empfängt, muss sie in der Lage sein, diese zu interpretieren. Dies ist vergleichbar mit der Situation, in der zwei Personen, A und B, per Telefon kommunizieren: Ihr Gespräch wird über das Telefon übertragen. Die Sprache wird vom Telefon A in Form von Signalen codiert, über Telefonleitungen übertragen, erreicht das Telefon B und wird dort decodiert. Die Person B hört dann Sprache. Hier kommt der Begriff des Kommunikationsprotokolls ins Spiel: Wenn A Französisch spricht und B diese Sprache nicht versteht, können A und B keinen sinnvollen Dialog führen.
Daher müssen sich die beiden kommunizierenden Anwendungen über die Art des Dialogs einigen, den sie verwenden werden. Beispielsweise unterscheidet sich der Dialog mit einem Dienst ftp von dem mit einem Dienst pop: Diese beiden Dienste akzeptieren nicht dieselben Befehle. Sie verfügen über ein unterschiedliches Dialogprotokoll.
9.3.2. Die Merkmale des Protokolls TCP
Wir werden hier nur Netzwerkkommunikationen betrachten, die das Transportprotokoll TCP verwenden. Hier noch einmal die Merkmale dieses Protokolls:
- Der Prozess, der Daten senden möchte, baut zunächst eine Verbindung zu dem Prozess auf, der die zu sendenden Informationen empfangen soll. Diese Verbindung wird zwischen einem Port des sendenden Rechners und einem Port des empfangenden Rechners hergestellt. Zwischen den beiden Ports entsteht so ein virtueller Pfad, der ausschließlich den beiden Prozessen vorbehalten ist, die die Verbindung hergestellt haben.
- Alle vom Quellprozess gesendeten Pakete folgen diesem virtuellen Pfad und kommen in der Reihenfolge an, in der sie gesendet wurden.
- Die übertragenen Informationen werden kontinuierlich gesendet. Der sendende Prozess übermittelt Informationen in seinem eigenen Rhythmus. Diese werden nicht unbedingt sofort gesendet: Das Protokoll TCP wartet, bis genügend Daten vorhanden sind, um sie zu senden. Sie werden in einer Struktur namens Segment TCP gespeichert. Sobald dieses Segment gefüllt ist, wird es an die Schicht IP weitergeleitet, wo es in ein Paket IP gekapselt wird.
- Jedes vom Protokoll TCP gesendete Segment ist nummeriert. Das empfangende Protokoll TCP überprüft, ob es die Segmente in der richtigen Reihenfolge erhält. Für jedes korrekt empfangene Segment sendet es eine Empfangsbestätigung an den Absender.
- Sobald der Absender diese Bestätigung erhält, teilt er dies dem sendenden Prozess mit. Dieser kann somit feststellen, dass ein Segment erfolgreich angekommen ist.
- Wenn das Protokoll TCP, das ein Segment gesendet hat, nach einer gewissen Zeit keine Empfangsbestätigung erhält, sendet es das betreffende Segment erneut und gewährleistet so die Qualität des Informationsübertragungsdienstes.
- Die zwischen den beiden kommunizierenden Prozessen hergestellte virtuelle Verbindung lautet full-duplex: Das bedeutet, dass die Informationen in beide Richtungen übertragen werden können. So kann der Zielprozess Empfangsbestätigungen senden, während der Quellprozess weiterhin Informationen versendet. Dies ermöglicht es beispielsweise dem Quellprotokoll TCP, mehrere Segmente zu senden, ohne auf eine Empfangsbestätigung zu warten. Stellt es nach einer gewissen Zeit fest, dass es die Empfangsbestätigung für ein bestimmtes Segment mit der Nummer n nicht erhalten hat, setzt es die Übertragung der Segmente an dieser Stelle fort.
9.3.3. Die Client-Server-Beziehung
Häufig ist die Kommunikation im Internet asymmetrisch: Rechner A initiiert eine Verbindung, um einen Dienst von Rechner B anzufordern: Er gibt an, dass er eine Verbindung zum Dienst SB1 von Rechner B herstellen möchte. Dieser akzeptiert oder lehnt ab. Wenn sie akzeptiert, kann Rechner A seine Anfragen an den Dienst SB1 senden. Diese müssen dem vom Dienst SB1 verstandenen Dialogprotokoll entsprechen. So entsteht ein Frage-Antwort-Dialog zwischen Rechner A, dem sogenannten Client, und Rechner B, dem sogenannten Server. Einer der beiden Partner wird die Verbindung beenden.
9.3.4. Architektur eines Clients
Die Architektur eines Netzwerkprogramms, das die Dienste einer Serveranwendung in Anspruch nimmt, sieht wie folgt aus:
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la machine B
attendre et récupérer la réponse
la traiter
fin tant que
finsi
9.3.5. Architektur eines Servers
Die Architektur eines Programms, das Dienste bereitstellt, sieht wie folgt aus:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que
Das Serverprogramm behandelt die erste Verbindungsanfrage eines Clients anders als dessen nachfolgende Anfragen zur Inanspruchnahme eines Dienstes. Das Programm erbringt den Dienst nicht selbst. Würde es dies tun, könnte es während der Dauer des Dienstes keine Verbindungsanfragen mehr entgegennehmen, und die Clients würden nicht bedient werden. Es geht daher anders vor: Sobald eine Verbindungsanfrage am Listening-Port empfangen und angenommen wird, erstellt der Server eine Task, die für die Bereitstellung des vom Client angeforderten Dienstes zuständig ist. Dieser Dienst wird über einen anderen Port des Serverrechners bereitgestellt, der als Dienstport bezeichnet wird. Auf diese Weise können mehrere Clients gleichzeitig bedient werden. Eine Dienst-Task hat folgende Struktur:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
9.3.6. Die Klasse TcpClient
Die Klasse TcpClient ist die geeignete Klasse zur Darstellung des Clients eines Dienstes TCP. Sie ist wie folgt definiert:

Die für uns relevanten Konstruktoren, Methoden und Eigenschaften sind die folgenden:
erstellt eine TCP-Verbindung mit dem Server, der auf dem angegebenen Port (port) des angegebenen Rechners (hostname) läuft. Beispiel: new TcpClient("istia.univ-angers.fr", 80), um eine Verbindung zum Port 80 des Rechners istia.univ-angers.fr herzustellen | |
beendet die Verbindung zum TCP-Server | |
erhält einen NetworkStream-Stream zum Lesen und Schreiben zum Server. Dieser Stream ermöglicht den Austausch zwischen Client und Server. |
9.3.7. Die Klasse NetworkStream
Die Klasse NetworkStream repräsentiert den Netzwerk-Datenstrom zwischen Client und Server. Die Klasse ist wie folgt definiert:

Die Klasse NetworkStream ist von der Klasse Stream abgeleitet. Viele Client-Server-Anwendungen tauschen Textzeilen aus, die mit den Zeilenendezeichen „\r\n“ enden. Daher ist es sinnvoll, die Objekte StreamReader und StreamWriter zu verwenden, um diese Zeilen im Netzwerkstrom zu lesen und zu schreiben. Wenn zwei Rechner miteinander kommunizieren, befindet sich an jedem Ende der Verbindung ein Objekt TcpClient. Die Methode GetStream dieses Objekts ermöglicht den Zugriff auf den Netzwerkstrom (NetworkStream), der die beiden Rechner verbindet. Wenn also ein Rechner M1 mithilfe eines Objekts TcpClient eine Verbindung zu einem Rechner M2 hergestellt hat und diese beiden Rechner Textzeilen austauschen, kann sie ihre Lese- und Schreibströme wie folgt erstellen:
Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true
Die Anweisung
bedeutet, dass der Schreibstrom von client1 nicht über einen Zwischenspeicher geleitet wird, sondern direkt ins Netzwerk gelangt. Dieser Punkt ist wichtig. Normalerweise erwartet client1 eine Antwort, wenn es eine Textzeile an seinen Partner sendet. Diese Antwort wird jedoch nie eintreffen, wenn die Zeile tatsächlich auf dem Rechner M1 gepuffert und nie gesendet wurde. Um eine Textzeile an den Rechner M2 zu senden, schreibt man:
Um die Antwort von M2 zu lesen, schreibt man:
9.3.8. Grundlegende Architektur eines Internet-Clients
Wir verfügen nun über die Elemente, um die Grundarchitektur eines Internet-Clients zu beschreiben:
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim demande As String = Nothing ' demande du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' Es wird eine Verbindung zum Dienst hergestellt, der auf Port P des Rechners M läuft
client = New TcpClient(nomServeur, port)
' Es werden die Ein- und Ausgabeströme des Clients erstellt TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Anfrage-Antwort-Zyklus
While True
' Die Anfrage wird vorbereitet
demande = ...
' die Anfrage wird an den Server gesendet
OUT.WriteLine(demande)
' Die Antwort vom Server wird gelesen
réponse = [IN].ReadLine()
' die Antwort wird verarbeitet
...
End While
' Fertig
client.Close()
Catch ex As Exception
' die Ausnahme wird behandelt
...
End Try
9.3.9. Die Klasse TcpListener
Die Klasse TcpListener ist die geeignete Klasse zur Darstellung eines Dienstes TCP. Sie ist wie folgt definiert:

Die für uns relevanten Konstruktoren, Methoden und Eigenschaften sind die folgenden:
erstellt einen Dienst TCP, der (listen) auf Client-Anfragen an einem als Parameter übergebenen Port (port), dem sogenannten Listening-Port des lokalen Rechners mit der Adresse IP localadr. | |
akzeptiert die Anfrage eines Kunden. Gibt als Ergebnis ein Objekt TcpClient zurück, das einem anderen Port zugeordnet ist, dem sogenannten Service-Port. | |
startet das Abhören von Kundenanfragen | |
beendet das Abhören von Kundenanfragen |
9.3.10. Grundlegende Architektur eines Internet-Servers
Aus den bisherigen Ausführungen lässt sich die Grundstruktur eines Servers ableiten:
' Der Listener-Dienst wird erstellt
Dim ecoute As TcpListener = Nothing
Dim port As Integer = ...
Try
' Der Dienst wird erstellt
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' Er wird gestartet
ecoute.Start()
' Dienstschleife
Dim liaisonClient As TcpClient = Nothing
While not fini
' Warten auf einen Client
liaisonClient = ecoute.AcceptTcpClient()
' Der Dienst wird von einer anderen Aufgabe übernommen
Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
tache.Start()
End While
Catch ex As Exception
' Der Fehler wird gemeldet
....
End Try
' Ende des Dienstes
ecoute.Stop()
Die Klasse Service ist eine thread, die wie folgt aussehen könnte:
Public Class Service
Private liaisonClient As TcpClient ' liaison avec le client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
' Konstruktor
Public Sub New(ByVal liaisonClient As TcpClient, ...)
Me.liaisonClient = liaisonClient
...
End Sub
' Methode „run“
Public Sub Run()
' gibt den Dienst an den Client zurück
Try
' Eingabestrom
[IN] = New StreamReader(liaisonClient.GetStream())
' Ausgabestrom
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' Schleife: Leseanforderung/Schreibantwort
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' Die Anfrage wird bearbeitet
...
' die Antwort wird gesendet
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' nächste Anfrage
demande = [IN].ReadLine
End While
' Verbindung beendet
liaisonClient.Close()
Catch e As Exception
...
End Try
' Ende des Dienstes
End Sub
9.4. Beispiele
9.4.1. Echo-Server
Wir wollen einen Echo-Server schreiben, der aus einem DOS-Fenster mit dem folgenden Befehl gestartet wird:
Der Server läuft auf dem als Parameter übergebenen Port. Er sendet lediglich die Anfrage, die der Client an ihn gesendet hat, an diesen zurück. Das Programm sieht wie folgt aus:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' Aufruf: serveurEcho Port
' Echo-Server
' sendet die vom Client gesendete Zeile an diesen zurück
Public Class serveurEcho
Private Shared syntaxe As String = "Syntaxe : serveurEcho port"
' Hauptprogramm
Public Shared Sub Main(ByVal args() As String)
' Gibt es ein Argument?
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' Dieses Argument muss eine ganze Zahl > 0 sein
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' Der Listening-Dienst wird erstellt
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' nbre de clients traités
Try
' Der Dienst wird erstellt
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' Er wird gestartet
ecoute.Start()
' Weiterverfolgung
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
Console.Out.WriteLine(ecoute.LocalEndpoint)
' Dienstschleife
Dim liaisonClient As TcpClient = Nothing
While True
' Endlosschleife – wird mit Strg+C beendet
' Warten auf einen Client
liaisonClient = ecoute.AcceptTcpClient()
' Der Dienst wird von einer anderen Aufgabe übernommen
nbClients += 1
Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
tache.Start()
End While
' Wir gehen wieder auf die Anfragen ein
Catch ex As Exception
' Der Fehler wird gemeldet
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
' Ende des Dienstes
ecoute.Stop()
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
' -------------------------------------------------------
' Bedient einen Client des Echo-Servers
Public Class traiteClientEcho
Private liaisonClient As TcpClient ' liaison avec le client
Private numClient As Integer ' n° de client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
' Konstruktor
Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
Me.liaisonClient = liaisonClient
Me.numClient = numClient
End Sub
' Methode „run“
Public Sub Run()
' erbringt den Dienst für den Client
Console.Out.WriteLine(("Début de service au client " & numClient))
Try
' Eingabestrom
[IN] = New StreamReader(liaisonClient.GetStream())
' Ausgabestrom
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' Schleife „Anfrage lesen/Antwort schreiben“
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' Überwachung
Console.Out.WriteLine(("Client " & numClient & " : " & demande))
' Der Dienst wird beendet, wenn der Client ein Dateiendezeichen sendet
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' Der Dienst wird beendet, wenn der Client „Ende“ sendet
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
' nächste Anfrage
demande = [IN].ReadLine
End While
' Verbindung beendet
liaisonClient.Close()
Catch e As Exception
erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
End Try
' Ende des Dienstes
Console.Out.WriteLine(("Fin de service au client " & numClient))
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
Die Struktur des Servers entspricht der allgemeinen Architektur von TCP-Servern.
9.4.2. Ein Client für den Echo-Server
Wir schreiben nun einen Client für den zuvor beschriebenen Server. Er wird wie folgt aufgerufen:
Er verbindet sich mit dem Rechner nomServeur über den Port port und sendet dann Textzeilen an den Server, die dieser als Echo zurücksendet.
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class clientEcho
' Verbindung zu einem Echo-Server
' Jede über die Tastatur eingegebene Zeile wird dann als Echo zurückgegeben
Public Shared Sub Main(ByVal args() As String)
' Syntax
Const syntaxe As String = "pg machine port"
' Anzahl der Argumente
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' Der Name des Servers wird notiert
Dim nomServeur As String = args(0)
' Der Port muss eine ganze Zahl größer als 0 sein
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(1))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' man kann arbeiten
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim demande As String = Nothing ' demande du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' Man stellt eine Verbindung zu dem Dienst her, der auf Port P des Rechners M läuft
client = New TcpClient(nomServeur, port)
' Die Ein- und Ausgabeströme des Clients werden erstellt TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Anfrage-Antwort-Zyklus
While True
' Die Anfrage kommt von der Tastatur
Console.Out.Write("demande (fin pour arrêter) : ")
demande = Console.In.ReadLine()
' Sie wird an den Server gesendet
OUT.WriteLine(demande)
' Die Antwort vom Server wird gelesen
réponse = [IN].ReadLine()
' die Antwort wird verarbeitet
Console.Out.WriteLine(("Réponse : " + réponse))
' Fertig?
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
End While
' Es ist fertig
client.Close()
Catch ex As Exception
' die Ausnahme wird behandelt
erreur(ex.Message, 3)
End Try
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehler anzeigen
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
Die Struktur dieses Clients entspricht der allgemeinen Architektur der tcp.Voici-Clients. Die Ergebnisse wurden in der folgenden Konfiguration erzielt:
- Der Server wird auf Port 100 in einem DOS-Fenster gestartet
- Auf demselben Rechner werden zwei Clients in zwei weiteren DOS-Fenstern gestartet
Im Fenster von Client 1 erhält man folgende Ergebnisse:
dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]
In der des Kunden 2:
dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]
In der des Servers:
dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C
Es ist zu beachten, dass der Server tatsächlich in der Lage war, zwei Clients gleichzeitig zu bedienen.
9.4.3. Ein generischer Client TCP
Viele Dienste, die zu Beginn des Internets entstanden sind, funktionieren nach dem zuvor behandelten Echo-Server-Modell: Der Austausch zwischen Client und Server erfolgt über den Austausch von Textzeilen. Wir werden einen generischen TCP-Client schreiben, der wie folgt gestartet wird: cltgen server port
Dieser Client TCP stellt eine Verbindung zum Port port des Servers serveur her. Anschließend erstellt er zwei Threads:
- einen Thread, der die über die Tastatur eingegebenen Befehle liest und an den Server sendet
- ein Thread, der die Antworten des Servers liest und auf dem Bildschirm anzeigt
Warum zwei Threads, obwohl in der vorherigen Anwendung kein Bedarf dafür bestand? In dieser war das Dialogprotokoll bekannt: Der Client sendete eine einzige Zeile und der Server antwortete mit einer einzigen Zeile. Jeder Dienst hat sein eigenes Protokoll, und es gibt auch folgende Situationen:
- Der Client muss mehrere Textzeilen senden, bevor er eine Antwort erhält
- die Antwort eines Servers kann mehrere Textzeilen umfassen
Daher ist die Schleife „Senden einer einzelnen Zeile an den Server – Empfang einer einzelnen vom Server gesendeten Zeile“ nicht immer geeignet. Wir werden daher zwei voneinander getrennte Schleifen erstellen:
- eine Schleife zum Einlesen der über die Tastatur eingegebenen Befehle, die an den Server gesendet werden sollen. Der Benutzer signalisiert das Ende der Befehle mit dem Schlüsselwort fin.
- eine Schleife zum Empfangen und Anzeigen der Antworten des Servers. Dabei handelt es sich um eine Endlosschleife, die nur durch das Schließen der Netzwerkverbindung durch den Server oder durch den Benutzer über die Tastatur unterbrochen wird, indem dieser den Befehl fin eingibt.
Um diese beiden Schleifen voneinander zu trennen, benötigen wir zwei unabhängige Threads. Betrachten wir ein Ausführungsbeispiel, bei dem sich unser generischer TCP-Client mit einem Dienst namens SMTP (SendMail Transfer Protocol) verbindet. Dieser Dienst ist für die Weiterleitung von E-Mails an die Empfänger zuständig. Er läuft auf Port 25 und verwendet ein Dialogprotokoll, bei dem Textzeilen ausgetauscht werden.
dos>cltgen istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test
ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Erläutern wir diesen Client-Server-Austausch:
- Der Dienst SMTP sendet eine Begrüßungsnachricht, wenn sich ein Client bei ihm anmeldet:
- Einige Dienste verfügen über den Befehl help, der Hinweise zu den mit dem Dienst verwendbaren Befehlen gibt. Hier ist dies nicht der Fall. Die im Beispiel verwendeten Befehle SMTP lauten wie folgt:
- mail from: expéditeur, um die E-Mail-Adresse des Absenders der Nachricht anzugeben
- rcpt to: destinataire, um die E-Mail-Adresse des Empfängers der Nachricht anzugeben. Bei mehreren Empfängern wird der Befehl „rcpt to:“ so oft wie nötig für jeden einzelnen Empfänger wiederholt.
- Daten, die dem Server SMTP signalisieren, dass die Nachricht gesendet wird. Wie in der Antwort des Servers angegeben, handelt es sich dabei um eine Folge von Zeilen, die mit einer Zeile endet, die ausschließlich das Zeichen „.“ enthält. Eine Nachricht kann Kopfzeilen enthalten, die durch eine Leerzeile vom Nachrichtentext getrennt sind. In unserem Beispiel haben wir einen Betreff mit dem Schlüsselwort „Subject:“ angegeben:
- Sobald die Nachricht gesendet wurde, kann man dem Server mit dem Befehl „quit“ mitteilen, dass man fertig ist. Der Server schließt daraufhin die Verbindung réseau.Le. Der Lesethread kann dieses Ereignis erkennen und sich beenden.
- Der Benutzer gibt dann „fin“ über die Tastatur ein, um auch den Thread zum Lesen der über die Tastatur eingegebenen Befehle zu beenden.
Wenn man die empfangene E-Mail überprüft, sieht man Folgendes (Outlook):

Es ist zu beachten, dass der Dienst SMTP nicht erkennen kann, ob ein Absender gültig ist oder nicht. Daher kann man dem Feld from einer Nachricht niemals vertrauen. In diesem Fall existierte der Absender „machin@univ-angers.fr“ nicht. Dieser generische TCP-Client ermöglicht es uns, das Dialogprotokoll von Internetdiensten zu ermitteln und auf dieser Grundlage spezialisierte Klassen für Clients dieser Dienste zu erstellen. Lassen Sie uns das Kommunikationsprotokoll des Dienstes POP (Post Office Protocol) untersuchen, mit dem man seine auf einem Server gespeicherten E-Mails abrufen kann. Es läuft auf Port 110.
dos>cltgen istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<-- by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<-- Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<-- by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<-- Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2 Tl. : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 , Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Die wichtigsten Befehle lauten wie folgt:
- user login, wobei man seinen Benutzernamen auf dem Rechner angibt, auf dem sich die E-Mails befinden
- pass password, wobei man das zum vorherigen Login gehörende Passwort eingibt
- list, um eine Liste der Nachrichten in Form von Nummer und Größe in Byte zu erhalten
- retr i, um die Nachricht Nr. i zu lesen
- quit, um die Verbindung zu beenden.
Sehen wir uns nun das Dialogprotokoll zwischen einem Client und einem Webserver an, der in der Regel auf Port 80 läuft:
dos>cltgen istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Ein Webclient sendet seine Befehle nach folgendem Schema an den Server:
Erst nach Erhalt der leeren Zeile antwortet der Webserver. In diesem Beispiel haben wir nur einen Befehl verwendet:
der den Server nach URL /index.html abfragt und angibt, dass er mit dem Protokoll HTTP Version 1.0 arbeitet. Die aktuellste Version dieses Protokolls ist 1.1. Das Beispiel zeigt, dass der Server mit dem Inhalt der Datei index.html geantwortet und anschließend die Verbindung geschlossen hat, da der Thread zum Lesen der Antworten beendet wurde. Vor dem Senden des Inhalts der Datei index.html hat der Webserver eine Reihe von Headern gesendet, die mit einer Leerzeile endeten:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
Die Zeile <html> ist die erste Zeile der Datei /index.html. Das Vorstehende wird als HTTP-Header (HyperText-Übertragungsprotokoll) bezeichnet. Wir werden hier nicht näher auf diese Header eingehen, aber man sollte bedenken, dass unser generischer Client Zugriff darauf gewährt, was zum Verständnis hilfreich sein kann. Die erste Zeile zum Beispiel:
gibt an, dass der angesprochene Webserver das Protokoll HTTP/1.1 versteht und die angeforderte Datei tatsächlich gefunden hat (200 OK), wobei 200 ein HTTP-Antwortcode ist. Die Zeilen
teilen dem Client mit, dass er 11251 Bytes erhalten wird, die den Text HTML (HyperText Markup Language) darstellen, und dass die Verbindung nach Abschluss der Übertragung geschlossen wird. Wir haben hier also einen sehr praktischen TCP-Client. Tatsächlich existiert dieser Client bereits auf den Rechnern, wo er den Namen telnet trägt, aber es war interessant, ihn selbst zu schreiben. Das Programm für den generischen TCP-Client lautet wie folgt:
' Namensräume
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' die Klasse
Public Class clientTcpGénérique
' erhält als Parameter die Merkmale eines Dienstes in der Form
' Server-Port
' stellt eine Verbindung zum Dienst her
' erstellt einen Thread zum Einlesen von Befehlen, die über die Tastatur eingegeben werden
' Diese werden an den Server gesendet
' erstellt einen Thread zum Lesen der Antworten vom Server
' diese werden auf dem Bildschirm angezeigt
' Das Ganze endet mit dem über die Tastatur eingegebenen Befehl „fin“
Public Shared Sub Main(ByVal args() As String)
' Syntax
Const syntaxe As String = "pg serveur port"
' Anzahl der Argumente
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' Der Name des Servers wird notiert
Dim serveur As String = args(0)
' Der Port muss eine ganze Zahl > 0 sein
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(1))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
Dim client As TcpClient = Nothing
' Es können Probleme auftreten
Try
' Verbindung zum Dienst herstellen
client = New TcpClient(serveur, port)
Catch ex As Exception
' Fehler
Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
' Ende
Return
End Try
' Die Lese-/Schreib-Threads werden erstellt
Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))
' Die Ausführung der beiden Threads wird gestartet
thSend.Start()
thReceive.Start()
' Ende des Haupt-Threads
Return
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
Public Class clientSend
' Klasse zum Einlesen von über die Tastatur eingegebenen Befehlen
' und diese über einen an den Konstruktor übergebenen TCP-Client an einen Server zu senden
Private client As TcpClient ' le client tcp
' Konstruktor
Public Sub New(ByVal client As TcpClient)
' Hier wird der TCP-Client übergeben
Me.client = client
End Sub
' Run-Methode des Threads
Public Sub Run()
' lokale Daten
Dim OUT As StreamWriter = Nothing ' flux d'écriture réseau
Dim commande As String = Nothing ' commande lue au clavier
' Fehlerbehandlung
Try
' Erstellung des Netzwerk-Schreibstroms
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Schleife zur Eingabe und zum Senden von Befehlen
Console.Out.WriteLine("Commandes : ")
While True
' Einlesen des über die Tastatur eingegebenen Befehls
commande = Console.In.ReadLine().Trim()
' Fertig?
If commande.ToLower() = "fin" Then
Exit While
End If
' Befehl an den Server senden
OUT.WriteLine(commande)
End While
Catch ex As Exception
' Fehler
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' Ende – Datenströme werden geschlossen
Try
OUT.Close()
client.Close()
Catch
End Try
' Ende des Threads melden
Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
End Sub
End Class
Public Class clientReceive
' Klasse, die für das Einlesen der für einen
' TCP-Client, der an den Konstruktor übergeben wurde
Private client As TcpClient ' le client tcp
' Hersteller
Public Sub New(ByVal client As TcpClient)
' TCP-Client wird notiert
Me.client = client
End Sub
'Hersteller
' Run-Methode des Threads
Public Sub Run()
' lokale Daten
Dim [IN] As StreamReader = Nothing ' flux lecture réseau
Dim réponse As String = Nothing ' réponse serveur
' Fehlerbehandlung
Try
' Erstellung des Netzwerk-Lesestroms
[IN] = New StreamReader(client.GetStream())
' Schleife zum Einlesen von Textzeilen aus dem Stream IN
While True
' Lesen des Netzwerkstroms
réponse = [IN].ReadLine()
' Datenstrom geschlossen?
If réponse Is Nothing Then
Exit While
End If
' Anzeige
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' Fehler
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' Ende – die Streams werden geschlossen
Try
[IN].Close()
client.Close()
Catch
End Try
' Ende des Threads melden
Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
End Sub
End Class
9.4.4. Ein generischer TCP-Server
Nun wenden wir uns einem Server zu,
- , der die von seinen Clients gesendeten Befehle auf dem Bildschirm anzeigt
- und ihnen als Antwort die Textzeilen zurücksendet, die ein Benutzer über die Tastatur eingegeben hat. Letzterer fungiert also als Server.
Das Programm wird mit folgendem Befehl gestartet: srvgen portEcoute, wobei portEcoute der Port ist, über den sich die Clients verbinden müssen. Der Kundenservice wird von zwei Threads gewährleistet:
- ein Thread, der sich ausschließlich dem Lesen der vom Client gesendeten Textzeilen widmet
- ein Thread, der sich ausschließlich dem Einlesen der vom Benutzer über die Tastatur eingegebenen Antworten widmet. Dieser signalisiert mit dem Befehl „fin“, dass er die Verbindung zum Client beendet.
Der Server erstellt zwei Threads pro Client. Bei n Clients sind somit 2n Threads gleichzeitig aktiv. Der Server selbst wird niemals beendet, es sei denn, der Benutzer drückt die Tastenkombination Strg+C. Sehen wir uns einige Beispiele an.
Der Server wird auf Port 100 gestartet, und wir verwenden den generischen Client, um mit ihm zu kommunizieren. Das Client-Fenster sieht wie folgt aus:
dos>cltgen localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
Die Zeilen, die mit <-- beginnen, sind vom Server an den Client gesendet worden, die anderen vom Client an den Server. Das Serverfenster sieht wie folgt aus:
dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Die mit <-- beginnenden Zeilen sind diejenigen, die vom Client an den Server gesendet wurden. Die Zeilen mit „N:“ sind diejenigen, die vom Server an den Client Nr. N gesendet wurden. Der oben genannte Server ist noch aktiv, während Client 1 beendet wurde. Wir starten einen zweiten Client für denselben Server:
dos>cltgen localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
Das Serverfenster sieht dann wie folgt aus:
dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C
Simulieren wir nun einen Webserver, indem wir unseren generischen Server auf Port 88 starten:
Öffnen wir nun einen Browser und rufen wir die Seite URL http://localhost:88/exemple.html auf. Der Browser stellt dann eine Verbindung zum Port 88 des Rechners localhost her und fordert anschließend die Seite /exemple.html an:

Sehen wir uns nun das Fenster unseres Servers an:
dos>srvgen 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
So lassen sich die vom Browser gesendeten Header HTTP identifizieren. Auf diese Weise können wir nach und nach das Protokoll HTTP entschlüsseln. In einem früheren Beispiel hatten wir einen Web-Client erstellt, der lediglich den Befehl GET sendete. Das war damals ausreichend. Hier sehen wir, dass der Browser weitere Informationen an den Server sendet. Diese dienen dazu, dem Server mitzuteilen, um welche Art von Client es sich handelt. Man sieht auch, dass die Header HTTP mit einer Leerzeile enden. Erstellen wir eine Antwort für unseren Client. Der Benutzer an der Tastatur ist hier der eigentliche Server und kann die Antwort manuell erstellen. Erinnern wir uns an die Antwort eines Webservers aus einem früheren Beispiel:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
Versuchen wir, eine ähnliche Antwort zu geben:
...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]
Zeilen, die mit „2:“ beginnen, werden vom Server an Client Nr. 2 gesendet. Der Befehl fin beendet die Verbindung vom Server zum Client. Wir haben uns in unserer Antwort auf die folgenden HTTP-Header beschränkt:
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
Wir geben die Größe der Datei, die wir senden werden (Content-Length), nicht an, sondern geben lediglich an, dass wir die Verbindung (Connection: close) nach dem Senden dieser Datei schließen werden. Das reicht für den Browser aus. Sobald der Browser sieht, dass die Verbindung geschlossen wurde, weiß er, dass die Antwort des Servers abgeschlossen ist, und zeigt die Seite HTML an, die ihm gesendet wurde. Diese lautet wie folgt:
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
Der Benutzer beendet anschließend die Verbindung zum Client, indem er den Befehl fin eingibt. Der Browser erkennt daraufhin, dass die Antwort des Servers abgeschlossen ist, und kann sie nun anzeigen:

Wenn man oben den Befehl Affichage/Source eingibt, um zu sehen, was der Browser empfangen hat, erhält man:

Das ist genau das, was vom generischen Server gesendet wurde. Der Code des generischen Servers TCP lautet wie folgt:
' Namensräume
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class serveurTcpGénérique
' Hauptprogramm
Public Shared Sub Main(ByVal args() As String)
' empfängt den Listening-Port für Client-Anfragen
' erstellt einen Thread zum Lesen der Client-Anfragen
' diese werden auf dem Bildschirm angezeigt
' erstellt einen Thread zum Lesen von über die Tastatur eingegebenen Befehlen
' Diese werden als Antwort an den Kunden gesendet
' Das Ganze endet mit dem über die Tastatur eingegebenen Befehl „end“
Const syntaxe As String = "Syntaxe : pg port"
' Gibt es ein Argument?
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' Dieses Argument muss eine ganze Zahl > 0 sein
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' Der Listening-Dienst wird erstellt
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' nbre de clients traités
Try
' Der Dienst wird erstellt
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' Er wird gestartet
ecoute.Start()
' Weiterverfolgung
Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))
' Dienstschleife für Clients
Dim client As TcpClient = Nothing
While True ' boucle infinie - sera arrêtée par Ctrl-C
' Warten auf einen Kunden
client = ecoute.AcceptTcpClient()
' Der Dienst wird von separaten Threads ausgeführt
nbClients += 1
' Thread zum Lesen der Kundenanfragen
Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
' Thread zum Lesen der vom Benutzer über die Tastatur eingegebenen Antworten
Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))
' Die Ausführung der beiden Threads wird gestartet
thSend.Start()
thReceive.Start()
End While
' Es wird wieder auf Anfragen gewartet
Catch ex As Exception
' Der Fehler wird gemeldet
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
Public Class serveurSend
' Klasse zum Einlesen von über die Tastatur eingegebenen Antworten
' und diese über einen an den Konstruktor übergebenen TCP-Client an einen Client zu senden
Private client As TcpClient ' le client tcp
Private numClient As Integer ' n° de client
' Konstruktor
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' Hier wird der TCP-Client angegeben
Me.client = client
' und dessen Nummer
Me.numClient = numClient
End Sub
' Run-Methode des Threads
Public Sub Run()
' lokale Daten
Dim OUT As StreamWriter = Nothing ' flux d'écriture réseau
Dim réponse As String = Nothing ' réponse lue au clavier
' Nachverfolgung
Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
' Fehlerbehandlung
Try
' Erstellung des Netzwerk-Schreibstroms
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Schleife zur Eingabe und zum Versand von Befehlen
While True
' Kundenidentifikation
Console.Out.Write((numClient & " : "))
' Einlesen der über die Tastatur eingegebenen Antwort
réponse = Console.In.ReadLine().Trim()
' Fertig?
If réponse.ToLower() = "fin" Then
Exit While
End If
' Antwort an den Server senden
OUT.WriteLine(réponse)
End While
' Nächste Antwort
Catch ex As Exception
' Fehler
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' Ende – Streams werden geschlossen
Try
OUT.Close()
client.Close()
Catch
End Try
' Ende des Threads melden
Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
End Sub
End Class
Public Class serveurReceive
' Klasse, die für das Einlesen der an den Server gesendeten Textzeilen zuständig ist
' über einen an den Konstruktor übergebenen TCP-Client
Private client As TcpClient ' le client tcp
Private numClient As Integer ' n° de client
' Konstruktor
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' der TCP-Client wird notiert
Me.client = client
' und dessen Nummer
Me.numClient = numClient
End Sub
' Run-Methode des Threads
Public Sub Run()
' lokale Daten
Dim [IN] As StreamReader = Nothing ' flux lecture réseau
Dim réponse As String = Nothing ' réponse serveur
' Nachverfolgung
Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
' Fehlerbehandlung
Try
' Erstellung des Netzwerk-Lese-Streams
[IN] = New StreamReader(client.GetStream())
' Schleife zum Einlesen von Textzeilen aus dem Datenstrom IN
While True
' Lesen des Netzwerk-Streams
réponse = [IN].ReadLine()
' Datenstrom geschlossen?
If réponse Is Nothing Then
Exit While
End If
' Anzeige
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' Fehler
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' Ende – die Streams werden geschlossen
Try
[IN].Close()
client.Close()
Catch
End Try
' Ende des Threads melden
Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
End Sub
End Class
9.4.5. Ein Webclient
Im vorherigen Beispiel haben wir einige der HTTP-Header gesehen, die ein Browser gesendet hat:
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Wir werden einen Web-Client schreiben, dem ein URL als Parameter übergeben wird und der den vom Server gesendeten Text auf dem Bildschirm anzeigt. Wir gehen davon aus, dass dieser das Protokoll HTTP 1.1 unterstützt. Von den oben genannten Headern werden wir nur die folgenden verwenden:
- Der erste Header gibt an, welche Seite wir
- der zweite, welchen Server wir abfragen
- die dritte, dass der Server die Verbindung nach der Antwort schließen soll.
Wenn wir oben GET durch HEAD ersetzen, sendet uns der Server nur die Header HTTP und nicht die Seite HTML.
Unser Webclient wird wie folgt aufgerufen: clientweb URL cmd, wobei URL diegewünschte URL und „cmd“ eines der beiden Schlüsselwörter GET oder HEAD, um anzugeben, ob nur die Header (HEAD) oder auch der Inhalt der Seite (GET) haben möchten. Schauen wir uns ein erstes Beispiel an. Wir starten den Server IIS und anschließend den Webclient auf demselben Rechner:
dos>clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private
Die Antwort
bedeutet, dass die angeforderte Seite an einen anderen Ort verschoben wurde (also von URL). Die neue Adresse URL wird durch den Header „Location:“ angegeben:
Wenn wir im Aufruf des Web-Clients GET anstelle von HEAD verwenden:
dos>clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private
<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
Wir erhalten das gleiche Ergebnis wie mit HEAD, zusätzlich den Hauptteil der Seite HTML. Das Programm lautet wie folgt:
' Namensräume
Imports System
Imports System.Net.Sockets
Imports System.IO
Public Class clientWeb1
' fordert eine URL an
' zeigt deren Inhalt auf dem Bildschirm an
Public Shared Sub Main(ByVal args() As String)
' Syntax
Const syntaxe As String = "pg URI GET/HEAD"
' Anzahl der Argumente
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' die angeforderte URI wird notiert
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' Überprüfung der Gültigkeit des URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI ist falsch
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' Bestellungsprüfung
If commande <> "GET" And commande <> "HEAD" Then
' Fehlerhafte Bestellung
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' Es kann gearbeitet werden
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' Verbindung zum Server herstellen
client = New TcpClient(uri.Host, uri.Port)
' Eingangs- und Ausgangsströme des Kunden werden erstellt TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Anfrage an URL – Senden der Header HTTP
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' Die Antwort wird gelesen
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' Die Antwort wird verarbeitet
Console.Out.WriteLine(réponse)
' die Antwort wird gelesen
réponse = [IN].ReadLine()
End While
' Fertig
client.Close()
Catch e As Exception
' Ausnahme wird behandelt
erreur(e.Message, 4)
End Try
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beenden mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
Die einzige Neuerung in diesem Programm ist die Verwendung der Klasse „Uri“. Das Programm erhält einen URL (Uniform Resource Locator) oder URI (Uniform Resource Identifier) in der Form http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... Die Klasse „Uri“ ermöglicht es uns, die Zeichenkette des URL in ihre verschiedenen Elemente zu zerlegen. Aus der als Parameter übergebenen Zeichenkette URIstring wird ein Uri-Objekt erstellt:
' Gültigkeitsprüfung von URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI ist falsch
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
Wenn die als Parameter übergebene Zeichenfolge „URI“ keine gültige „URI“-Zeichenfolge ist (Fehlen des Protokolls, des Servers usw.), wird eine Ausnahme ausgelöst. Auf diese Weise können wir die Gültigkeit des empfangenen Parameters überprüfen. Sobald das URI-Objekt erstellt wurde, haben wir Zugriff auf die verschiedenen Elemente dieses URIs. Wenn also das Objekt uri aus dem vorherigen Code aus der Zeichenfolge http://serveur:port/cheminPageHTML?param1=val1;param2=val2;... erstellt wurde, erhalten wir:
uri.Host=serveur, uri.Port=port, uri.Path=cheminPageHTML, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.
9.4.6. Webclient, der Weiterleitungen verwaltet
Der vorherige Web-Client verarbeitet eine mögliche Weiterleitung von URL, die er angefordert hat, nicht. Der folgende Client verarbeitet sie.
- Er liest die erste Zeile der vom Server gesendeten Header HTTP, um zu überprüfen, ob dort die Zeichenfolge „302 Object moved“ enthalten ist, die eine Weiterleitung signalisiert
- Er liest die folgenden Header. Liegt eine Weiterleitung vor, sucht er nach der Zeile „Location: url“, die die neue URL der angeforderten Seite angibt, und notiert sich diese URL.
- Er zeigt den Rest der Antwort des Servers an. Liegt eine Weiterleitung vor, werden die Schritte 1 bis 3 mit der neuen URL wiederholt. Das Programm akzeptiert nicht mehr als eine Weiterleitung. Diese Begrenzung wird durch eine Konstante festgelegt, die geändert werden kann.
Hier ein Beispiel:
dos>clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private
<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781
<html>
<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>
Das Programm sieht wie folgt aus:
' Namensräume
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
' Web-Client-Klasse
Public Class clientWeb
' fordert eine URL an und zeigt deren Inhalt auf dem Bildschirm an
Public Shared Sub Main(ByVal args() As String)
' Syntax
Const syntaxe As String = "pg URI GET/HEAD"
' Anzahl der Argumente
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' Die angeforderte URI wird notiert
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' Überprüfung der Gültigkeit von URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI ist falsch
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try 'catch
' Überprüfung der Bestellung
If commande <> "GET" And commande <> "HEAD" Then
' Fehlerhafte Bestellung
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' Es kann weitergearbeitet werden
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim réponse As String = Nothing ' réponse du serveur
Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection acceptée
Dim nbRedirs As Integer = 0 ' nombre de redirections en cours
Dim premièreLigne As String ' 1ère ligne de la réponse
Dim redir As Boolean = False ' indique s'il y a redirection ou non
Dim locationString As String = "" ' la chaîne URI d'une éventuelle redirection
' Regulärer Ausdruck zum Auffinden einer Umleitung URL
Dim location As New Regex("^Location: (.+?)$") '
' Fehlerbehandlung
Try
' Es können mehrere URL abgefragt werden, falls Umleitungen vorliegen
While nbRedirs <= nbRedirsMax
' Verbindung zum Server herstellen
client = New TcpClient(uri.Host, uri.Port)
' Die Ein- und Ausgabeströme des Clients werden erstellt: TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Die Header HTTP werden gesendet, um den URL abzufragen
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' Die erste Zeile der Antwort wird gelesen
premièreLigne = [IN].ReadLine()
' Bildschirmanzeige
Console.Out.WriteLine(premièreLigne)
' Weiterleitung?
If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
' Es liegt eine Umleitung vor
redir = True
nbRedirs += 1
End If
' die folgenden HTTP-Header, bis die leere Zeile gefunden wird, die das Ende der Header signalisiert
Dim locationFound As Boolean = False
réponse = [IN].ReadLine()
While réponse <> ""
' die Antwort wird angezeigt
Console.Out.WriteLine(réponse)
' Wenn eine Weiterleitung vorliegt, wird nach dem Header „Location“ gesucht
If redir And Not locationFound Then
' die Zeile wird mit dem relationalen Ausdruck „location“ verglichen
Dim résultat As Match = location.Match(réponse)
If résultat.Success Then
' Wurde ein Treffer gefunden, wird der Umleitungs-URL notiert
locationString = résultat.Groups(1).Value
' Es wird vermerkt, dass ein Treffer gefunden wurde
locationFound = True
End If
End If
' nächste Zeile
réponse = [IN].ReadLine()
End While
' folgende Zeilen der Antwort
Console.Out.WriteLine(réponse)
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' Die Antwort wird angezeigt
Console.Out.WriteLine(réponse)
' nächste Zeile
réponse = [IN].ReadLine()
End While
' die Verbindung wird geschlossen
client.Close()
' Sind wir fertig?
If Not locationFound Or nbRedirs > nbRedirsMax Then
Exit While
End If
' Es muss eine Weiterleitung durchgeführt werden – die neue URI wird erstellt
URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
uri = New Uri(URIstring)
' Weiterverfolgung
Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
End While
Catch e As Exception
' Ausnahme wird behandelt
erreur(e.Message, 4)
End Try
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beenden mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
9.4.7. Steuerberechnungsserver
Wir greifen die Übung IMPOTS wieder auf, die bereits in verschiedenen Formen behandelt wurde. Erinnern wir uns an die letzte Version. Es wurde eine Steuerklasse angelegt. Ihre Attribute sind drei Arrays mit Zahlen:
Public Class impôt
' die für die Steuerberechnung erforderlichen Daten
' stammen aus einer externen Quelle
Private limites(), coeffR(), coeffN() as double
Die Klasse verfügt über zwei Konstruktoren:
- einen Konstruktor, dem die drei für die Steuerberechnung erforderlichen Daten-Arrays übergeben werden
// Hersteller 1
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' initialisiert die drei Grenzwert-Arrays coeffR, coeffN anhand
' der an den Konstruktor übergebenen Parameter
- ein Hersteller, dem der Name DSN aus einer Datenbank mit dem Namen ODBC übergeben wird
' Konstruktor 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initialisiert die drei Begrenzungs-Arrays coeffR und coeffN anhand
' des Inhalts der Tabelle „Timpots“ aus der Datenbank ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN sind die drei Spalten dieser Tabelle
' kann eine Ausnahme auslösen
Es wurde ein Testprogramm geschrieben:
dos>vbc /r:impots.dll testimpots.vb
dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F
Hier befanden sich das Testprogramm und das Objekt impôt auf demselben Rechner. Wir beabsichtigen, das Testprogramm und das Objekt impôt auf unterschiedlichen Rechnern zu installieren. Wir erhalten eine Client-Server-Anwendung, bei der das entfernte Objekt impôt als Server fungiert. Die neue Klasse heißt ServeurImpots und ist von der Klasse impôt abgeleitet:
Public Class ServeurImpots
Inherits impôt
' Attribute
Private portEcoute As Integer ' le port d'écoute des demandes clients
Private actif As Boolean ' état du serveur
' Konstruktor
Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
' hier ist der Listening-Port vermerkt
Me.portEcoute = portEcoute
' derzeit inaktiv
actif = False
' erstellt und startet einen Thread zum Einlesen der über die Tastatur eingegebenen Befehle
' Der Server wird über diese Befehle verwaltet
Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
threadLecture.Start()
End Sub
Der einzige neue Parameter im Konstruktor ist der Port, auf dem auf Client-Anfragen gewartet wird. Die übrigen Parameter werden direkt an die Basisklasse impôt übergeben. Der Steuer-Server wird über Befehle gesteuert, die über die Tastatur eingegeben werden. Daher erstellen wir einen Thread, um diese Befehle zu lesen. Es gibt zwei mögliche Befehle: start zum Starten des Dienstes und stop zum endgültigen Beenden des Dienstes. Die Methode admin, die diese Befehle verarbeitet, lautet wie folgt:
Public Sub admin()
' liest die über die Tastatur eingegebenen Server-Verwaltungsbefehle
' in einer Endlosschleife
Dim commande As String = Nothing
While True
' Eingabeaufforderung
Console.Out.Write("Serveur d'impôts>")
' Befehl einlesen
commande = Console.In.ReadLine().Trim().ToLower()
' Befehl ausführen
If commande = "start" Then
' aktiv?
If actif Then
'Fehler
Console.Out.WriteLine("Le serveur est déjà actif")
Else
' Der Listener-Dienst wird gestartet
Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
threadEcoute.Start()
End If
Else
If commande = "stop" Then
' Ende aller Ausführungsthreads
Environment.Exit(0)
Else
' Fehler
Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
End If
End If
End While
End Sub
Wenn der über die Tastatur eingegebene Befehl start lautet, wird ein Thread gestartet, der auf Client-Anfragen wartet. Wenn der eingegebene Befehl stop lautet, werden alle Threads beendet. Der Listening-Thread führt die Methode ecoute aus:
Public Sub ecoute()
' Thread zum Abhören von Client-Anfragen
' Der Listening-Dienst wird erstellt
Dim ecoute As TcpListener = Nothing
Try
' Der Dienst wird erstellt
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
' Er wird gestartet
ecoute.Start()
' Weiterverfolgung
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))
' Dienstschleife
Dim liaisonClient As TcpClient = Nothing
While True ' boucle infinie
' Warten auf einen Client
liaisonClient = ecoute.AcceptTcpClient()
' Der Dienst wird von einer anderen Aufgabe übernommen
Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
threadClient.Start()
End While
' man kehrt zum Abhören von Anfragen zurück
Catch ex As Exception
' Der Fehler wird gemeldet
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beenden mit Fehler
Environment.Exit(exitCode)
End Sub
Es handelt sich um einen klassischen TCP-Server, der auf dem Port portEcoute lauscht. Die Client-Anfragen werden von der Methode Run eines Objekts verarbeitet, an das zwei Parameter übergeben werden:
- das Objekt TcpClient, über das der Client erreicht wird
- das Objekt impôt this, das den Zugriff auf die Methode this.calculer zur Steuerberechnung ermöglicht.
' -------------------------------------------------------
' Betreuung eines Kunden des Steuer-Servers
Public Class traiteClientImpots
Private liaisonClient As TcpClient ' liaison avec le client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
Private objImpôt As impôt ' objet Impôt
' Konstruktor
Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
Me.liaisonClient = liaisonClient
Me.objImpôt = objImpôt
End Sub
Die Methode Run verarbeitet die Anfragen der Kunden. Diese können zwei Formen annehmen:
- Berechnung für Verheiratete (j/n) nbEnfants salaireAnnuel
- Finanzberechnungen
Form 1 ermöglicht die Berechnung einer Steuer, Form 2 beendet die Client-Server-Verbindung.
' Methode „Run“
Public Sub Run()
' erbringt den Dienst für den Client
Try
' Eingangsfluss
[IN] = New StreamReader(liaisonClient.GetStream())
' Ausgabestrom
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' Versenden einer Willkommensnachricht an den Kunden
OUT.WriteLine("Bienvenue sur le serveur d'impôts")
' Schleife zum Lesen der Anfrage und Schreiben der Antwort
Dim demande As String = Nothing
Dim champs As String() = Nothing ' les éléments de la demande
Dim commande As String = Nothing ' la commande du client : calcul ou fincalculs
demande = [IN].ReadLine()
While Not (demande Is Nothing)
' Die Anfrage wird in Felder zerlegt
champs = Regex.Split(demande.Trim().ToLower(), "\s+")
' Zwei akzeptierte Anfragen: Berechnung und Abschluss der Berechnungen
commande = champs(0)
Dim erreur As Boolean = False
If commande <> "calcul" And commande <> "fincalculs" Then
' Client-Fehler
OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
End If
If commande = "calcul" Then
calculerImpôt(champs)
End If
If commande = "fincalculs" Then
' Abschiedsmeldung an den Client
OUT.WriteLine("Au revoir...")
' Freigabe der Ressourcen
Try
OUT.Close()
[IN].Close()
liaisonClient.Close()
Catch
End Try
' Ende
Return
End If
' neue Anfrage
demande = [IN].ReadLine()
End While
Catch e As Exception
erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
End Try
End Sub
Die Steuerberechnung erfolgt über die Methode calculerImpôt, die als Parameter das Feldarray der vom Client gestellten Anfrage erhält. Die Gültigkeit der Anfrage wird überprüft und gegebenenfalls die Steuer berechnet und an den Client zurückgesendet.
' Steuerberechnung
Public Sub calculerImpôt(ByVal champs() As String)
' Antrag bearbeiten: Berechnung für Verheiratete nbEnfants salaireAnnuel
' in Felder in der Feldtabelle aufgeschlüsselt
Dim marié As String = Nothing
Dim nbEnfants As Integer = 0
Dim salaireAnnuel As Integer = 0
' Gültigkeit der Argumente
Try
' Es sind mindestens 4 Felder erforderlich
If champs.Length <> 4 Then
Throw New Exception
End If
' verheiratet
marié = champs(1)
If marié <> "o" And marié <> "n" Then
Throw New Exception
End If
' Kinder
nbEnfants = Integer.Parse(champs(2))
' Gehalt
salaireAnnuel = Integer.Parse(champs(3))
Catch
OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
' Fertig
Exit Sub
End Try
' Die Steuer kann berechnet werden
Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
' Antwort wird an den Kunden gesendet
OUT.WriteLine(impot.ToString)
End Sub
' Fehleranzeige
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Abbruch mit Fehler
Environment.Exit(exitCode)
End Sub
Diese Klasse wird kompiliert von
, wobei impots.dll den Code der Klasse impôt enthält. Ein Testprogramm könnte wie folgt aussehen:
' Namensräume
Imports System
Imports System.IO
Imports Microsoft.VisualBasic
Public Class testServeurImpots
Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"
' Hauptprogramm
Public Shared Sub Main(ByVal args() As String)
' Es sind 6 Argumente erforderlich
If args.Length <> 6 Then
erreur(syntaxe, 1)
End If
' Der Port muss eine ganze Zahl > 0 sein
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' Der Steuer-Server wird erstellt
Try
Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
Catch ex As Exception
'Fehler
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
End Sub
' Anzeige der Fehler
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Class
Dem Testprogramm werden die Daten übergeben, die zur Erstellung eines Objekts ServeurImpots erforderlich sind, und darauf aufbauend erstellt es dieses Objekt. Dieses Testprogramm wird kompiliert durch:
Hier ist ein erster Test:
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
Die Zeile
Erstellt ein Objekt ServeurImpots, das noch nicht auf Client-Anfragen wartet. Der über die Tastatur eingegebene Befehl „start“ löst diesen Empfang aus. Der Befehl „stop“ beendet den Server. Verwenden wir nun einen Client. Wir verwenden den zuvor erstellten generischen Client. Der Server wird gestartet:
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
Der generische Client wird in einem weiteren DOS-Fenster gestartet:
Man sieht, dass der Client die Begrüßungsnachricht des Servers erfolgreich empfangen hat. Wir senden weitere Befehle:
x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<-- syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Wir kehren zum Serverfenster zurück, um den Server zu beenden:















