8. Programmierung TCP-IP
8.1. Allgemeines
8.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 dem Namen 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 von 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
8.1.2. Das Modell 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 Netzwerkfunktion 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 gemäß weiterer Regeln an die Schicht Session weitergeleitet und so weiter, bis die Informationen auf dem physischen Träger ankommen und physisch 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 Aspekte 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. |
8.1.3. Das Modell TCP/IP
Das Modell OSI ist ein ideales Modell, das bisher noch nicht realisiert wurde. Die Protokollsuite TCP/IP kommt dem in folgender Form nahe:

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 Daten übertragen möchte, überwacht das Kabel – dabei erkennt er, ob eine Trägerwelle vorhanden ist oder nicht; das Vorhandensein einer Trägerwelle würde bedeuten, dass gerade eine Übertragung stattfindet. Dies ist die Technik CSMA (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 hat eine Adresse – hier als physikalische Adresse bezeichnet –, die auf der Karte vermerkt ist, über die er mit dem Kabel verbunden ist. Diese Adresse wird als Ethernet-Adresse des Rechners bezeichnet.
Netzwerkschicht
Auf dieser Schicht finden wir die Protokolle IP, ICMP, ARP und RARP.
IP (Internetprotokoll) | Übermittelt Pakete zwischen zwei Knoten im Netzwerk |
ICMP (Internet Control Message Protocol) | 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. |
ARP (Address Resolution Protocol) | stellt die Zuordnung von der Internetadresse eines Rechners zur physikalischen Adresse des Rechners her |
RARP (Reverse Address Resolution Protocol) | stellt die Zuordnung von der physischen Adresse eines Rechners zur Internetadresse des Rechners her |
Transport-/Sitzungsschicht
In dieser Schicht finden sich folgende Protokolle:
TCP (Transmission Control Protocol) | Gewährleistet eine zuverlässige Übertragung von Informationen zwischen zwei Clients |
UDP (User Datagram Protocol) | Gewährleistet eine unzuverlässige Übertragung 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 |
8.1.4. Funktionsweise der Internetprotokolle
Anwendungen, die in der Umgebung TCP/IP 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 Folge von Bytes, die an die Schicht transport. übertragen werden sollen
- 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.
8.1.5. Adressierungsprobleme 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 voneinander 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 IP-Protokoll jedes Netzwerks übernimmt die Umwandlung zwischen IP-Adresse und physikalischer Adresse in beide Richtungen.
Die IP-Adressen müssen alle unterschiedlich sein. Offizielle Stellen sind für deren Vergabe zuständig. Tatsächlich vergeben diese Stellen eine Adresse für lokale Netzwerke, zum Beispiel 193.49.144.0 für das Netzwerk der naturwissenschaftlichen Fakultät in Angers. Der Administrator dieses Netzwerks kann dann die Adressen IP 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.
8.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 1en besteht, handelt es sich um eine Broadcast-Adresse: Diese Adresse bezeichnet alle Knoten im Netzwerk.
. 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.
8.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 IP-Adresse 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 zwischen physikalischer Adresse und 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.
8.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 verarbeitet 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 Pakete anschließend über das physikalische Netzwerk gesendet wird. Sie werden erst an ihrem endgültigen Zielort wieder zusammengesetzt.
8.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 Empfängeradresse IP.
- 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
Unter indirekter Weiterleitung versteht man die Weiterleitung eines Pakets mit der Kennung IP an ein Ziel, das sich in einem anderen Netzwerk befindet als das des Absenders. In diesem Fall unterscheiden sich die Netzwerkadressanteile der Adressen IP des Quell- und des Zielrechners. Der Quellrechner erkennt dies. 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.
8.1.6.2. Fehler- und Kontrollmeldungen
Ebenfalls in der Netzwerkschicht, also auf derselben Ebene wie das Protokoll IP, existiert das Protokoll ICMP (Internet Control Message Protocol). Es dient dazu, Meldungen über den internen Betrieb des Netzwerks zu senden: ausgefallene Knoten, Stau 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.
8.1.7. Die Transportschicht: die Protokolle UDP und TCP
8.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.
8.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.
8.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
Dies erfordert ein Verzeichnis, das die Zuordnung zwischen Rechner und Adresse herstellt, z. B. IP. 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.
Der Rechner DPX2/320 der Universität Angers wurde als Lagaffe benannt, während ein PC und ein 486DX50 als liny benannt 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:
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:
Schließlich kann er innerhalb der Domäne „univ-Angers“ einfach wie folgt bezeichnet werden:
Eine Anwendung kann einen Rechner also anhand seines Namens referenzieren. Letztendlich muss man jedoch die Internetadresse dieses Rechners ermitteln. Wie wird dies 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 entwickelte Protokoll (MicroSystems) 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.
8.1.9. Fazit
In dieser Einführung haben wir einige Grundzüge der Internetprotokolle vorgestellt. Wer sich näher mit diesem Thema befassen möchte, kann das hervorragende Buch von Douglas Comer lesen:
Titel: TCP/IP: Architektur, Protokolle, Anwendungen.
Autor: Douglas COMER
Verlag: InterEditions
8.2. Verwaltung von Netzwerkadressen in Java
8.2.1. Definition
Jeder Rechner im Internet wird durch eine eindeutige Adresse oder einen eindeutigen Namen identifiziert. Diese beiden Elemente werden in Java von der Klasse InetAddress verwaltet, von der hier einige Methoden aufgeführt sind:
gibt die 4 Bytes der Adresse IP der aktuellen Instanz InetAddress zurück | |
liefert die Adresse IP der aktuellen Instanz InetAddress | |
gibt den Internetnamen der aktuellen Instanz InetAddress an | |
gibt die Adress-ID IP bzw. den Internetnamen der aktuellen Instanz InetAddress an | |
erstellt die Instanz InetAddress des durch „Host“ bezeichneten Rechners. Löst eine Ausnahme aus, wenn „Host“ unbekannt ist. „Host“ kann der Internetname eines Rechners oder dessen Adresse IP in der Form I1.I2.I3.I4 sein | |
erstellt die Instanz InetAddress des Rechners, auf dem das Programm ausgeführt wird, das diese Anweisung enthält. |
8.2.2. Einige Beispiele
8.2.2.1. Den lokalen Rechner identifizieren
import java.net.*;
public class localhost{
public static void main (String arg[]){
try{
InetAddress adresse=InetAddress.getLocalHost();
byte[] IP=adresse.getAddress();
System.out.print("IP=");
int i;
for(i=0;i<IP.length-1;i++) System.out.print(IP[i]+".");
System.out.println(IP[i]);
System.out.println("adresse="+adresse.getHostAddress());
System.out.println("nom="+adresse.getHostName());
System.out.println("identité="+adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getLocalHost : "+e);
}// Ende von try
}// Ende von main
}// Ende der Klasse
Die Ergebnisse der Ausführung lauten wie folgt:
Jeder Rechner hat eine interne Adresse IP, die 127.0.0.1 lautet. Wenn ein Programm diese Netzwerkadresse verwendet, greift es auf den Rechner zu, auf dem es ausgeführt wird. Der Vorteil dieser Adresse besteht darin, dass sie keine Netzwerkkarte erfordert. Man kann daher Netzwerkprogramme testen, ohne mit einem Netzwerk verbunden zu sein. Eine weitere Möglichkeit, den lokalen Rechner zu bezeichnen, ist die Verwendung des Namens „localhost“.
8.2.2.2. Einen beliebigen Rechner identifizieren
import java.net.*;
public class getbyname{
public static void main (String arg[]){
String nomMachine;
// das Argument wird abgerufen
if(arg.length==0)
nomMachine="localhost";
else nomMachine=arg[0];
// Versuch, die Adresse des Rechners abzurufen
try{
InetAddress adresse=InetAddress.getByName(nomMachine);
System.out.println("IP : "+ adresse.getHostAddress());
System.out.println("nom : "+ adresse.getHostName());
System.out.println("identité : "+ adresse);
} catch (UnknownHostException e){
System.out.println ("Erreur getByName : "+e);
}// Ende try
}// Ende von main
}// Ende der Klasse
Mit dem Java-Aufruf **getbyname** erhält man folgende Ergebnisse:
Mit dem Aufruf „java getbyname shiva.istia.univ-angers.fr“ erhält man:
Mit dem Java-Aufruf „getbyname“ www.ibm.com erhält man:
8.3. Meldungen TCP-IP
8.3.1. Allgemeines

Wenn eine Anwendung AppA auf einem Rechner A mit einer Anwendung AppB auf einem 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. Denn die Maschinen A und B werden miteinander „kommunizieren“. Was sie sich mitteilen, wird in die Protokolle TCP-IP eingekapselt. 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, telefonisch miteinander kommunizieren: Ihr Gespräch wird über das Telefon übertragen. Die Sprache wird vom Telefon A in Form von Signalen codiert, über Telefonleitungen übertragen und erreicht das Telefon B, wo sie decodiert wird. Person B hört dann Sprache. Hier kommt der Begriff des Dialogprotokolls 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 anwenden werden. So ist beispielsweise der Dialog mit einem Dienst ftp nicht derselbe wie mit einem Dienst pop: Diese beiden Dienste akzeptieren nicht dieselben Befehle. Sie verfügen über ein unterschiedliches Kommunikationsprotokoll.
8.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 gesendeten Informationen haben einen kontinuierlichen Charakter. Der sendende Prozess sendet Informationen in seinem eigenen Tempo. 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 ist nummeriert. Das empfangende Protokoll TCP überprüft, ob es die Segmente in der richtigen Reihenfolge empfängt. Für jedes korrekt empfangene Segment sendet es eine Empfangsbestätigung an den Absender.
- Sobald der Absender diese 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.
8.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.
8.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
8.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 Aufgabe, die dafür zuständig ist, den vom Client angeforderten Dienst bereitzustellen. Dieser Dienst wird auf einem anderen Port des Serverrechners bereitgestellt, dem sogenannten Dienstport. 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
8.3.6. Die Socket-Klasse
8.3.6.1. Définition
Das grundlegende Werkzeug, das von Programmen zur Kommunikation über das Internet verwendet wird, ist die socket. Dieses englische Wort bedeutet „Steckdose“. Es wird hier im übertragenen Sinne als „Netzwerksteckdose“ verwendet. Damit eine Anwendung Informationen über das Internet senden und empfangen kann, benötigt sie eine Netzwerksteckdose, eine socket. Dieses Tool wurde ursprünglich in den Unix-Versionen der Universität Berkeley entwickelt. Es wurde seitdem auf alle Unix-Systeme sowie in die Windows-Welt portiert. Es ist auch auf Java-Virtual-Machines in zwei Formen verfügbar: die Klasse Socket für Client-Anwendungen und die Klasse ServerSocket für Server-Anwendungen. Wir erläutern hier einige der Konstruktoren und Methoden der Klasse Socket:
stellt eine Fernverbindung zum Port port des Rechners host her |
gibt die Nummer des vom Socket verwendeten lokalen Ports zurück | |||
gibt die Nummer des Remote-Ports zurück, mit dem der Socket verbunden ist | |||
gibt die lokale Adresse zurück, mit der der Socket verknüpft ist | |||
gibt die Remote-Adresse InetAddress zurück, mit der der Socket verbunden ist | |||
gibt einen Eingabestrom zurück, über den die vom Remote-Partner gesendeten Daten gelesen werden können | |||
gibt einen Ausgabestrom zurück, über den Daten an den Remote-Partner gesendet werden können | |||
schließt den Eingabestrom des Sockets | |||
schließt den Ausgabestrom des Sockets | |||
schließt den Socket und seine Ein- und Ausgabeströme | |||
gibt eine Zeichenkette zurück, die den Socket „repräsentiert“ | |||
8.3.6.2. Herstellen einer Verbindung zu einem Server
Wir haben gesehen, dass ein Rechner A zwei Informationen benötigt, um eine Verbindung zu einem Dienst auf einem Rechner B herzustellen:
- die Adresse IP oder den Namen des Rechners B
- die Portnummer, unter der der gewünschte Dienst läuft
Der Konstruktor
erstellt einen Socket und verbindet ihn mit dem Rechner host auf dem Port port. Dieser Konstruktor löst in verschiedenen Fällen eine Ausnahme aus:
- falsche Adresse
- falscher Port
- Anfrage abgelehnt
- …
Wir müssen diese Ausnahme behandeln:
Socket sClient=null;
try{
sClient=new Socket(host,port);
} catch(Exception e){
// Die Verbindung ist fehlgeschlagen – der Fehler wird behandelt
….
}
Wenn die Verbindungsanfrage erfolgreich ist, wird dem Client lokal ein Port zugewiesen, über den er mit Rechner B kommunizieren kann. Sobald die Verbindung hergestellt ist, kann dieser Port mit der folgenden Methode ermittelt werden:
Wenn die Verbindung erfolgreich hergestellt wird, haben wir gesehen, dass der Server seinerseits den Dienst durch einen anderen Prozess bereitstellen lässt, der auf einem sogenannten Dienstport läuft. Diese Portnummer kann mit der folgenden Methode ermittelt werden:
8.3.6.3. Informationen über das Netzwerk senden
Mit der folgenden Methode kann ein Schreibstrom über den Socket und damit über das Netzwerk erzeugt werden:
Alles, was in diesen Stream gesendet wird, wird am Service-Port des Servers empfangen. Viele Anwendungen verfügen über einen Dialog in Form von Textzeilen, die mit einem Zeilenumbruch enden. Daher ist die Methode println in solchen Fällen sehr praktisch. Man wandelt dann den Ausgabestrom OutputStream in den Strom PrintWriter um, der über die Methode println verfügt. Das Schreiben kann eine Ausnahme auslösen.
8.3.6.4. Informationen aus dem Netzwerk lesen
Mit der folgenden Methode kann ein Lesestrom der am Socket eingehenden Informationen abgerufen werden:
Alles, was in diesem Stream gelesen wird, stammt vom Dienstport des Servers. Für Anwendungen mit einem Dialog in Form von Textzeilen, die mit einem Zeilenumbruch enden, empfiehlt sich die Verwendung der Methode readLine. Dazu wandelt man den Eingabestrom InputStream in den Strom BufferedReader um, der über die Methode readLine() verfügt. Beim Einlesen kann eine Ausnahme auftreten.
8.3.6.5. Schließen der Verbindung
Dies erfolgt mit der Methode:
Die Methode kann eine Ausnahme auslösen. Die verwendeten Ressourcen, insbesondere der Netzwerkport, werden freigegeben.
8.3.6.6. Die Architektur des Clients
Wir verfügen nun über die Elemente, um die grundlegende Architektur eines Internet-Clients zu beschreiben:
Socket sClient=null;
try{
// Verbindung zum Dienst auf Port P des Rechners M wird hergestellt
sClient=new Socket(M,P);
// Die Ein- und Ausgabeströme des Client-Sockets werden erstellt
BufferedReader in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
PrintWriter out=new PrintWriter(sClient.getOutputStream(),true);
// Anfrage-Antwort-Schleife
boolean fini=false;
String demande;
String réponse;
while (! fini){
// Die Anfrage wird vorbereitet
demande=…
// Die Anfrage wird gesendet
out.println(demande);
// Die Antwort wird gelesen
réponse=in.readLine();
// Antwort wird verarbeitet
…
}
// Fertig
sClient.close();
} catch(Exception e){
// die Ausnahme wird behandelt
….
}
Um das Beispiel nicht zu verkomplizieren, haben wir darauf verzichtet, die verschiedenen Ausnahmetypen zu behandeln, die vom Konstruktor Socket oder den Methoden readline, getInputStream, getOutputStream und close ausgelöst werden. Alles wurde in einer einzigen Ausnahme zusammengefasst.
8.3.7. Die Klasse ServerSocket
8.3.7.1. Définition
Diese Klasse dient der serverseitigen Verwaltung von Sockets. Im Folgenden werden einige der Konstruktoren und Methoden dieser Klasse näher erläutert:
Erstellt einen Listening-Socket auf dem Port port | |
ebenso, legt jedoch die Warteschlangengröße auf count fest und die maximale Anzahl der in die Warteschlange gestellten Client-Verbindungen auf c.a.d, falls der Server bei Eintreffen der Client-Verbindung ausgelastet ist. |
Gibt die Nummer des vom Socket verwendeten Listening-Ports zurück | |
gibt die lokale Adresse InetAddress zurück, mit der der Socket verknüpft ist | |
versetzt den Server in den Verbindungswartemodus (blockierender Vorgang). Bei Eintreffen einer Client-Verbindung gibt er einen Socket zurück, über den der Dienst für den Client bereitgestellt wird. | |
schließt den Socket und seine E/A-Ströme | |
gibt eine Zeichenkette zurück, die den Socket „repräsentiert“ | |
schließt den Service-Socket und gibt die damit verbundenen Ressourcen frei |
8.3.7.2. Öffnen des Dienstes
Dies erfolgt mit den beiden Konstruktoren:
port ist der Listening-Port des Dienstes: der Port, an den die Clients ihre Verbindungsanfragen senden. count ist die maximale Größe der Warteschlange des Dienstes (standardmäßig 50), in der die Verbindungsanfragen der Clients gespeichert werden, auf die der Server noch nicht geantwortet hat. Wenn die Warteschlange voll ist, werden eingehende Verbindungsanfragen abgelehnt. Beide Konstruktoren lösen eine Ausnahme aus.
8.3.7.3. Annahme einer Verbindungsanfrage
Wenn ein Client eine Verbindungsanfrage an den Listening-Port des Dienstes sendet, akzeptiert dieser sie mit der Methode:
Diese Methode gibt eine Instanz von Socket zurück: Dies ist der Dienst-Socket, über den der Dienst bereitgestellt wird, meist durch einen anderen Task. Die Methode kann eine Ausnahme auslösen.
8.3.7.4. Lesen/Schreiben über den Dienst-Socket
Da der Service-Socket eine Instanz der Klasse Socket ist, verweisen wir auf die vorangegangenen Abschnitte, in denen dieses Thema behandelt wurde.
8.3.7.5. Den Kunden identifizieren
Sobald der Service-Socket erhalten wurde, kann der Client mit der Methode
der Klasse Socket identifiziert werden. Man erhält dann Zugriff auf die Adresse IP und den Namen des Kunden.
8.3.7.6. Den Dienst schließen
Dies erfolgt mit der Methode
der Klasse ServerSocket. Dadurch werden die belegten Ressourcen, insbesondere der Listening-Port, freigegeben. Die Methode kann eine Ausnahme auslösen.
8.3.7.7. Grundlegende Architektur eines Servers
Aus den bisherigen Ausführungen lässt sich die Grundstruktur eines Servers wie folgt beschreiben:
SocketServer sEcoute=null;
try{
// Dienst wird gestartet
int portEcoute=…
int maxConnexions=…
sEcoute=new ServerSocket(portEcoute,maxConnexions);
// Verarbeitung der Verbindungsanfragen
boolean fini=false;
Socket sService=null;
while( ! fini){
// Bearbeitung und Annahme eines Antrags
sService=sEcoute.accept();
// Der Dienst wird von einer anderen Aufgabe ausgeführt, an die der Dienst-Socket übergeben wird
new Service(sService).start();
// Es wird wieder auf Verbindungsanfragen gewartet
}
// Fertig – der Dienst wird beendet
sEcoute.close();
} catch (Exception e){
// Die Ausnahme wird behandelt
…
}
Die Klasse Service ist eine thread, die wie folgt aussehen könnte:
public class Service extends Thread{
Socket sService; // Der Service-Socket
// Konstruktor
public Service(Socket S){
sService=S;
}
// Ausführung
public void run(){
try{
// Eingabe- und Ausgabeströme werden erstellt
BufferedReader in=new BufferedReader(new InputStreamReader(sService.getInputStream()));
PrinttWriter out=new PrintWriter(sService.getOutputStream(),true);
// Anfrage-Antwort-Schleife
boolean fini=false;
String demande;
String réponse;
while (! fini){
// die Anfrage wird gelesen
demande=in.readLine();
// die Anfrage wird verarbeitet
…
// die Antwort wird vorbereitet
réponse=…
// die Antwort wird gesendet
out.println(réponse);
}
// Fertig
sService.close();
} catch(Exception e){
// die Ausnahme wird behandelt
….
}// try
} // Ausführen
8.4. Anwendungen
8.4.1. Echo-Server
Wir wollen einen Echo-Server schreiben, der aus einem Fenster mit der Nummer DOS mit dem folgenden Befehl gestartet wird:
Der Server läuft auf dem als Parameter übergebenen Port. Er sendet dem Client lediglich die Anfrage zurück, die dieser an ihn gesendet hat, zusammen mit seiner Identität (IP+Name). Er akzeptiert zwei Verbindungen in seiner Warteschlange. Damit sind alle Bestandteile eines TCP-Servers vorhanden. Das Programm lautet wie folgt:
// Aufruf: serveurEcho Port
// Echo-Server
// sendet die vom Client gesendete Zeile an diesen zurück
import java.net.*;
import java.io.*;
public class serveurEcho{
public final static String syntaxe="Syntaxe : serveurEcho port";
public final static int nbConnexions=2;
// Hauptprogramm
public static void main (String arg[]){
// Gibt es ein Argument?
if(arg.length != 1)
erreur(syntaxe,1);
// Dieses Argument muss eine ganze Zahl > 0 sein
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// Der Listening-Socket wird erstellt
ServerSocket ecoute=null;
try{
ecoute=new ServerSocket(port,nbConnexions);
} catch (Exception e){
erreur("Erreur lors de la création de la socket d'écoute ("+e+")",3);
}
// Weiter
System.out.println("Serveur d'écho lancé sur le port " + port);
// Service-Schleife
boolean serviceFini=false;
Socket service=null;
while (! serviceFini){
// Warten auf einen Client
try{
service=ecoute.accept();
} catch (IOException e){
erreur("Erreur lors de l'acceptation d'une connexion ("+e+")",4);
}
// Die Verbindung wird identifiziert
try{
System.out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// Der Dienst wird von einer anderen Aufgabe übernommen
new traiteClientEcho(service).start();
}// Ende der while-Schleife
}// Ende von „main“
// Anzeige der Fehler
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// identifiziert
private static String identifie(InetAddress Host){
// Host-Identifikation
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// Ende der Klasse
// erbringt den Dienst für einen Client des Echo-Servers
class traiteClientEcho extends Thread{
private Socket service; // Dienst-Socket
private BufferedReader in; // Eingabestrom
private PrintWriter out; // Ausgabestrom
// Konstruktor
public traiteClientEcho(Socket service){
this.service=service;
}
// Methode „run“
public void run(){
// Erstellung der Eingabe- und Ausgabeströme
try{
in=new BufferedReader(new InputStreamReader(service.getInputStream()));
} catch (IOException e){
erreur("Erreur lors de la création du flux déentrée de la socket de service ("+e+")",1);
}// Ende try
try{
out=new PrintWriter(service.getOutputStream(),true);
} catch (IOException e){
erreur("Erreur lors de la création du flux de sortie de la socket de service ("+e+")",1);
}// Ende try
// Die Identifikation der Verbindung wird an den Client gesendet
try{
out.println("Client ["+identifie(service.getInetAddress())+","+
service.getPort()+"] connecté au serveur [" + identifie (InetAddress.getLocalHost())
+ "," + service.getLocalPort() + "]");
} catch (Exception e) {
erreur("identification liaison",1);
}
// Schleife: Lesen der Anfrage/Schreiben der Antwort
String demande,reponse;
try{
// Der Dienst wird beendet, wenn der Client ein Ende-der-Datei-Zeichen sendet
while ((demande=in.readLine())!=null){
// Echo der Anfrage
reponse="["+demande+"]";
out.println(reponse);
// Der Dienst wird beendet, wenn der Client „Ende“ sendet
if(demande.trim().toLowerCase().equals("fin")) break;
}// Ende der while-Schleife
} catch (IOException e){
erreur("Erreur lors des échanges client/serveur ("+e+")",3);
}// Ende von „try“
// Der Socket wird geschlossen
try{
service.close();
} catch (IOException e){
erreur("Erreur lors de la fermeture de la socket de service ("+e+")",2);
}// Ende try
}// Ende von „run“
// Anzeige der Fehler
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}// Ende des Fehlers
// Identifizierung
private String identifie(InetAddress Host){
// Host-Identifikation
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// Ende der Klasse
Die beiden für den Dienst erforderlichen Klassen wurden in einer einzigen Quelldatei zusammengefasst. Nur eine davon, nämlich die mit der Funktion main, verfügt über das Attribut public. Die Struktur des Servers entspricht der allgemeinen Architektur von TCP-Servern. Es wurde eine Methode (identifie) hinzugefügt, mit der die Verbindung zwischen dem Server und einem Client identifiziert werden kann. Hier sind einige Ergebnisse:
Der Server wird mit dem Befehl gestartet
Er zeigt dann im Kontrollfenster die folgende Meldung an:
Um diesen Server zu testen, wird das Programm telnet verwendet, das sowohl unter Unix als auch unter Windows verfügbar ist. Telnet ist ein universeller TCP-Client, der für alle Server geeignet ist, die in ihrer Kommunikation Textzeilen akzeptieren, die mit einem Zeilenendezeichen enden. Dies ist bei unserem Echo-Server der Fall. Wir starten einen ersten telnet-Client unter Windows (in diesem Beispiel Windows 2000), indem wir telnet in ein DOS-Fenster eingeben:
DOS>telnet
Microsoft (R) Windows 2000 (TM) version 5.00 (numéro 2195)
Client Telnet Microsoft
Client Telnet numéro 5.00.99203.1
Le caractère d'échappement est 'CTRL+$'
Microsoft Telnet> help
Les commandes peuvent être abrégées. Les commandes prises en charge sont :
close ferme la connexion en cours
display affiche les paramètres d'opération
open ouvre une connexion à un site
quit quitte telnet
set définit les options (entrez 'set ?' pour afficher la liste)
status affiche les informations d'état
unset annule les options (entrez 'unset ?' pour afficher la liste)
? ou help affiche des informations d'aide
Microsoft Telnet> set ?
NTLM Active l'authentification NTLM.
LOCAL_ECHO Active l'écho local.
TERM x (où x est ANSI, VT100, VT52 ou VTNT))
CRLF Envoi de CR et de LF
Microsoft Telnet> set local_echo
Microsoft Telnet> open localhost 187
Das Programm telnet gibt standardmäßig keine Rückmeldung zu den über die Tastatur eingegebenen Befehlen aus. Um diese Rückmeldung zu erhalten, geben Sie folgenden Befehl ein:
Um eine Verbindung zum Server herzustellen und dabei den Port des Echo-Dienstes (187) sowie die Adresse des Rechners, auf dem er läuft (localhost), anzugeben, gibt man folgenden Befehl ein:
Im DOS-Fenster des Clients wird dann folgende Meldung angezeigt:
Im Serverfenster erscheint die Meldung:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Hier bezeichnen tahe und localhost denselben Rechner. Im Client-Fenster telnet kann man Textzeilen eingeben. Der Server gibt diese als Echo zurück:
Client [127.0.0.1,tahe,1059] connectÚ au serveur [127.0.0.1,tahe,187]
je suis là
[je suis là]
au revoir
[au revoir]
Es ist zu beachten, dass der Client-Port (1059) zwar korrekt erkannt wird, der Dienstport (187) jedoch mit dem Listening-Port (187) identisch ist, was unerwartet ist. Man hätte nämlich erwarten können, den Port des Dienst-Sockets zu erhalten und nicht den Listening-Port. Es müsste überprüft werden, ob unter Unix die gleichen Ergebnisse erzielt werden. Starten wir nun einen zweiten Client: telnet. Das Serverfenster sieht nun wie folgt aus:
Serveur d'écho lancé sur le port 187
Client [127.0.0.1,tahe,1059] connecté au serveur [127.0.0.1,tahe,187]
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
Im Fenster des zweiten Clients können wir ebenfalls Textzeilen eingeben:
Client [127.0.0.1,tahe,1060] connecté au serveur [127.0.0.1,tahe,187]
ligne1
[ligne1]
ligne2
[ligne2]
Man sieht also, dass der Echo-Server mehrere Clients gleichzeitig bedienen kann. Die Clients telnet können beendet werden, indem man das DOS-Fenster schließt, in dem sie ausgeführt werden.
8.4.2. Ein Java-Client für den Echo-Server
Im vorherigen Abschnitt haben wir einen telnet-Client verwendet, um den Echo-Dienst zu testen. Nun schreiben wir unseren eigenen Client:
// Aufruf: clientEcho Maschinenport
// Client des Echo-Servers
// sendet Zeilen an den Server, der sie zurückgibt
import java.net.*;
import java.io.*;
public class clientEcho{
public final static String syntaxe="Syntaxe : clientEcho machine port";
// Hauptprogramm
public static void main (String arg[]){
// Gibt es zwei Argumente?
if(arg.length != 2)
erreur(syntaxe,1);
// Das erste Argument muss der Name eines existierenden Rechners sein
String machine=arg[0];
InetAddress serveurAddress=null;
try{
serveurAddress=InetAddress.getByName(machine);
} catch (Exception e){
erreur(syntaxe+"\nMachine "+machine+" inaccessible (" + e +")",2);
}
// Der Port muss eine ganze Zahl größer als 0 sein
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(arg[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\nPort incorrect ("+E+")",3);
// Es wird eine Verbindung zum Server hergestellt
Socket sClient=null;
try{
sClient=new Socket(machine,port);
} catch (Exception e){
erreur("Erreur lors de la création de la socket de communication ("+e+")",4);
}
// Die Verbindung wird identifiziert
try{
System.out.println("Client : Client ["+identifie(InetAddress.getLocalHost())+","+
sClient.getLocalPort()+"] connecté au serveur [" + identifie (sClient.getInetAddress())
+ "," + sClient.getPort() + "]");
} catch (Exception e) {
erreur("identification liaison ("+e+")",5);
}
// Erstellung des Lesestroms für die über die Tastatur eingegebenen Zeilen
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
erreur("Création du flux d'entrée clavier ("+e+")",6);
}
// Erstellung des Eingabestroms für den Client-Socket
BufferedReader in=null;
try{
in=new BufferedReader(new InputStreamReader(sClient.getInputStream()));
} catch (Exception e){
erreur("Création du flux d'entrée de la socket client("+e+")",7);
}
// Erstellung des Ausgabestroms für den Client-Socket
PrintWriter out=null;
try{
out=new PrintWriter(sClient.getOutputStream(),true);
} catch (Exception e){
erreur("Création du flux de sortie de la socket ("+e+")",8);
}
// Anfrage-Antwort-Schleife
boolean serviceFini=false;
String demande=null;
String reponse=null;
// Die vom Server unmittelbar nach der Verbindung gesendete Nachricht wird gelesen
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
// Anzeige der Antwort
System.out.println("Serveur : " +reponse);
while (! serviceFini){
// Lesen einer über die Tastatur eingegebenen Zeile
System.out.print("Client : ");
try{
demande=IN.readLine();
} catch (Exception e){
erreur("Lecture ligne ("+e+")",9);
}
// Anfrage über das Netzwerk senden
try{
out.println(demande);
} catch (Exception e){
erreur("Envoi demande ("+e+")",10);
}
// Warten auf/Lesen der Antwort
try{
reponse=in.readLine();
} catch (IOException e){
erreur("Lecture réponse ("+e+")",4);
}
// Anzeige der Antwort
System.out.println("Serveur : " +reponse);
// Ist es fertig?
if(demande.trim().toLowerCase().equals("fin")) serviceFini=true;
}
// Es ist fertig
try{
sClient.close();
} catch(Exception e){
erreur("Fermeture socket ("+e+")",11);
}
}// Hand
// Anzeige der Fehler
public static void erreur(String msg, int exitCode){
System.err.println(msg);
System.exit(exitCode);
}
// Identifizieren
private static String identifie(InetAddress Host){
// Host-ID
String ipHost=Host.getHostAddress();
String nomHost=Host.getHostName();
String idHost;
if (nomHost == null) idHost=ipHost;
else idHost=ipHost+","+nomHost;
return idHost;
}
}// Ende der Klasse
Die Struktur dieses Clients entspricht der allgemeinen Architektur der tcp-Clients. Hier wurden die verschiedenen möglichen Ausnahmen einzeln behandelt, was das Programm schwerfälliger macht. Hier sind die Ergebnisse, die beim Testen dieses Clients erzielt wurden:
Client : Client [127.0.0.1,tahe,1045] connecté au serveur [127.0.0.1,localhost,187]
Serveur : Client [127.0.0.1,localhost,1045] connectÚ au serveur [127.0.0.1,tahe,187]
Client : 123
Serveur : [123]
Client : abcd
Serveur : [abcd]
Client : je suis là
Serveur : [je suis là]
Client : fin
Serveur : [fin]
Die Zeilen, die mit Client beginnen, sind die vom Client gesendeten Zeilen, und diejenigen, die mit Serveur beginnen, sind die vom Server zurückgesendeten Zeilen.
8.4.3. Ein generischer TCP-Client
Viele Dienste, die in den Anfängen des Internets entstanden sind, funktionieren nach dem zuvor beschriebenen 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: java cltTCPgenerique 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 Zeile vom Server“ 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 ihre Empfänger zuständig. Er läuft auf Port 25 und verwendet ein Dialogprotokoll, bei dem Textzeilen ausgetauscht werden.
Dos>java clientTCPgenerique 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.
- data, das dem Server SMTP signalisiert, 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 Netzwerkverbindung. 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 darf 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 Kommunikationsprotokoll von Internetdiensten zu ermitteln und auf dieser Grundlage spezialisierte Klassen für Clients dieser Dienste zu erstellen. Sehen wir uns das Kommunikationsprotokoll des Dienstes POP (Post Office Protocol) an, mit dem man seine auf einem Server gespeicherten E-Mails abrufen kann. Er läuft auf Port 110.
Dos> java clientTCPgenerique 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
- Passwort password eingeben, wobei das zum vorherigen Login gehörende Passwort angegeben wird
- list, um die 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 den Dialog 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> java clientTCPgenerique 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 kontaktierte Webserver das Protokoll HTTP/1.1 versteht und die angeforderte Datei erfolgreich 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. Er kann zweifellos weniger als das zuvor verwendete Programm telnet, aber es war interessant, ihn selbst zu schreiben. Das Programm für den generischen TCP-Client lautet wie folgt:
// importierte Pakete
import java.io.*;
import java.net.*;
public class clientTCPgenerique{
// 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“
// Instanzvariable
private static Socket client;
public static void main(String[] args){
// Syntax
final String syntaxe="pg serveur port";
// Anzahl der Argumente
if(args.length != 2)
erreur(syntaxe,1);
// Hier wird der Servername notiert
String serveur=args[0];
// Der Port muss eine ganze Zahl > 0 sein
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[1]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
client=null;
// Es können Probleme auftreten
try{
// Verbindung zum Dienst herstellen
client=new Socket(serveur,port);
}catch(Exception ex){
// Fehler
erreur("Impossible de se connecter au service ("+ serveur
+","+port+"), erreur : "+ex.getMessage(),3);
// Ende
return;
}//catch
// Lese-/Schreib-Threads werden erstellt
new ClientSend(client).start();
new ClientReceive(client).start();
// Ende des Haupt-Threads
return;
}// main
// Anzeige der Fehler
public static void erreur(String msg, int exitCode){
// Fehleranzeige
System.err.println(msg);
// Beenden mit Fehler
System.exit(exitCode);
}//Fehler
}//Klasse
class ClientSend extends Thread {
// Klasse zum Lesen von über die Tastatur eingegebenen Befehlen
// und diese über einen als Parameter übergebenen TCP-Client an einen Server zu senden
private Socket client; // der TCP-Client
// Konstruktor
public ClientSend(Socket client){
// Hier wird der TCP-Client angegeben
this.client=client;
}//Konstruktor
// Run-Methode des Threads
public void run(){
// lokale Daten
PrintWriter OUT=null; // Netzwerk-Schreibstrom
BufferedReader IN=null; // Tastatur-Datenstrom
String commande=null; // von der Tastatur gelesener Befehl
// Fehlerbehandlung
try{
// Erstellung des Netzwerk-Schreibstroms
OUT=new PrintWriter(client.getOutputStream(),true);
// Erstellung des Tastatureingabestroms
IN=new BufferedReader(new InputStreamReader(System.in));
// Schleife für die Eingabe und das Senden von Befehlen
System.out.println("Commandes : ");
while(true){
// Einlesen des über die Tastatur eingegebenen Befehls
commande=IN.readLine().trim();
// Fertig?
if (commande.toLowerCase().equals("fin")) break;
// Befehl an den Server senden
OUT.println(commande);
// Nächster Befehl
}//while
}catch(Exception ex){
// Fehler
System.err.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// Ende – Streams werden geschlossen
try{
OUT.close();client.close();
}catch(Exception ex){}
// das Ende des Threads wird gemeldet
System.out.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
}//run
}//Klasse
class ClientReceive extends Thread{
// Klasse, die für das Einlesen der Textzeilen zuständig ist, die für einen
// als Parameter übergebenen TCP-Client bestimmt sind
private Socket client; // der TCP-Client
// Konstruktor
public ClientReceive(Socket client){
// Der TCP-Client wird notiert
this.client=client;
}//Konstruktor
// Run-Methode des Threads
public void run(){
// lokale Daten
BufferedReader IN=null; // Netzwerk-Lese-Stream
String réponse=null; // Serverantwort
// Fehlerbehandlung
try{
// Erstellung des Netzwerk-Lese-Streams
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// Schleife zum Einlesen von Textzeilen aus dem Datenstrom IN
while(true){
// Lesen des Netzwerkstroms
réponse=IN.readLine();
// Datenstrom geschlossen?
if(réponse==null) break;
// Anzeige
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// Fehler
System.err.println("Réception : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// Ende – die Datenströme werden geschlossen
try{
IN.close();client.close();
}catch(Exception ex){}
// das Ende des Threads wird gemeldet
System.out.println("[Réception : fin du thread de lecture des réponses du serveur]");
}//run
}//Klasse
8.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 gestartet mit: java serveurTCPgenerique portEcoute, wobei portEcoute der Port ist, an den sich die Clients verbinden müssen. Die Bedienung der Clients wird von zwei Threads übernommen:
- ein Thread, der sich ausschließlich dem Einlesen 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:
E:\data\serge\MSNET\c#\Netzwerk\generischer TCP-Client> java clientTCPgenerique 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]
Zeilen, die mit <-- beginnen, wurden vom Server an den Client gesendet, die anderen vom Client an den Server. Das Serverfenster sieht wie folgt aus:
Dos> java serveurTCPgenerique 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 Zeilen, die mit <-- beginnen, sind diejenigen, die vom Client an den Server gesendet wurden. Die Zeilen N: sind die Zeilen, 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> java clientTCPgenerique 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> java serveurTCPgenerique 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:
Dos> java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Ö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>java serveurTCPgenerique 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 entdecken wir die vom Browser gesendeten Header HTTP. Dies ermöglicht es uns, das Protokoll HTTP nach und nach zu entschlüsseln. In einem früheren Beispiel hatten wir einen Web-Client erstellt, der lediglich den Befehl GET sendete. Das hatte ausgereicht. 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. Wir sehen auch, dass die Header HTTP mit einer leeren Zeile enden.
Erstellen wir eine Antwort für unseren Client. Der Benutzer an der Tastatur ist hier der eigentliche Server und kann eine 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>
Anschließend beendet der Benutzer 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 View/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 TCP-Servers lautet wie folgt:
// Pakete
import java.io.*;
import java.net.*;
public class serveurTCPgenerique{
// Hauptprogramm
public static void main (String[] args){
// empfängt Client-Anfragen über den Listening-Port
// erstellt einen Thread zum Lesen der Client-Anfragen
// diese werden auf dem Bildschirm angezeigt
// erstellt einen Thread zum Einlesen von über die Tastatur eingegebenen Befehlen
// Diese werden als Antwort an den Kunden gesendet
// Das Ganze endet mit dem Befehl „fin“, der über die Tastatur eingegeben wird
final String syntaxe="Syntaxe : pg port";
// Instanzvariable
// Gibt es ein Argument?
if(args.length != 1)
erreur(syntaxe,1);
// Der Port muss eine ganze Zahl > 0 sein
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// Der Listening-Dienst wird erstellt
ServerSocket ecoute=null;
int nbClients=0; // Anzahl der bedienten Clients
try{
// Der Dienst wird erstellt
ecoute=new ServerSocket(port);
// Weiterverfolgung
System.out.println("Serveur générique lancé sur le port " + port);
// Schleife für die Kundenbetreuung
Socket client=null;
while (true){ // Endlosschleife – wird mit Strg+C beendet
// Warten auf einen Client
client=ecoute.accept();
// Der Dienst wird von separaten Threads ausgeführt
nbClients++;
// Lese-/Schreib-Threads werden erstellt
new ServeurSend(client,nbClients).start();
new ServeurReceive(client,nbClients).start();
// Es wird wieder auf Anfragen gewartet
}// Ende der while-Schleife
}catch(Exception ex){
// Der Fehler wird gemeldet
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}// Ende von „main“
// Anzeige der Fehler
public static void erreur(String msg, int exitCode){
// Fehleranzeige
System.err.println(msg);
// Beenden mit Fehler
System.exit(exitCode);
}//Fehler
}//Klasse
class ServeurSend extends Thread{
// Klasse zum Einlesen von über die Tastatur eingegebenen Antworten
// und diese über einen an den Konstruktor übergebenen TCP-Client an einen Client zu senden
Socket client; // der TCP-Client
int numClient; // Client-Nr.
// Konstruktor
public ServeurSend(Socket client, int numClient){
// wird der TCP-Client notiert
this.client=client;
// sowie dessen Nummer
this.numClient=numClient;
}//Hersteller
// Run-Methode des Threads
public void run(){
// lokale Daten
PrintWriter OUT=null; // Netzwerk-Schreibstrom
String réponse=null; // von der Tastatur gelesene Antwort
BufferedReader IN=null; // Tastatur-Datenstrom
// Überwachung
System.out.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
// Fehlerbehandlung
try{
// Erstellung des Netzwerk-Schreibstroms
OUT=new PrintWriter(client.getOutputStream(),true);
// Erstellung des Tastatur-Datenstroms
IN=new BufferedReader(new InputStreamReader(System.in));
// Schleife „Befehlseingabe und -übertragung“
while(true){
// Kundenidentifikation
System.out.print("--> " + numClient + " : ");
// Auslesen der über die Tastatur eingegebenen Antwort
réponse=IN.readLine().trim();
// Fertig?
if (réponse.toLowerCase().equals("fin")) break;
// Antwort an den Server senden
OUT.println(réponse);
// Nächste Antwort
}//while
}catch(Exception ex){
// Fehler
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// Ende – Streams werden geschlossen
try{
OUT.close();client.close();
}catch(Exception ex){}
// das Ende des Threads wird gemeldet
System.out.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
}//run
}//Klasse
class ServeurReceive extends Thread{
// Klasse, die für das Einlesen der an den Server gesendeten Textzeilen zuständig ist
// über einen dem Konstruktor übergebenen TCP-Client
Socket client; // der TCP-Client
int numClient; // Client-Nr.
// Konstruktor
public ServeurReceive(Socket client, int numClient){
// der TCP-Client wird notiert
this.client=client;
// sowie dessen Nummer
this.numClient=numClient;
}//Hersteller
// Run-Methode des Threads
public void run(){
// lokale Daten
BufferedReader IN=null; // Netzwerk-Lese-Stream
String réponse=null; // Serverantwort
// Überwachung
System.out.println("Thread de lecture des demandes du client "+ numClient + " lancé");
// Fehlerbehandlung
try{
// Erstellung des Netzwerk-Lese-Streams
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// Schleife zum Einlesen von Textzeilen aus dem Datenstrom IN
while(true){
// Netzwerk-Stream lesen
réponse=IN.readLine();
// Strom geschlossen?
if(réponse==null) break;
// Anzeige
System.out.println("<-- "+réponse);
}//while
}catch(Exception ex){
// Fehler
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// Ende – die Datenströme werden geschlossen
try{
IN.close();client.close();
}catch(Exception ex){}
// das Ende des Threads wird gemeldet
System.out.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
}//run
}//Klasse
8.4.5. Ein Web-Client
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 Inhalt dieses URL auf dem Bildschirm anzeigt. Wir gehen davon aus, dass der für die URL kontaktierte Webserver das Protokoll HTTP 1.1 unterstützt. Von den oben genannten Headern verwenden wir nur die folgenden:
- Der erste Header gibt an, welche Seite wir
- der zweite, welchen Server wir abfragen
- die dritte, dass der Server die Verbindung nach der Antwort trennen 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: java 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>java 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>java 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:
// importierte Pakete
import java.io.*;
import java.net.*;
public class clientweb{
// fordert eine URL an
// zeigt deren Inhalt auf dem Bildschirm an
public static void main(String[] args){
// Syntax
final String syntaxe="pg URI GET/HEAD";
// Anzahl der Argumente
if(args.length != 2)
erreur(syntaxe,1);
// Es wird die angeforderte URI vermerkt
String URLString=args[0];
String commande=args[1].toUpperCase();
// Überprüfung der Gültigkeit von URI
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI ist falsch
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//Abfangen
// Überprüfung der Bestellung
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// Fehlerhafte Bestellung
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// Die relevanten Informationen werden aus dem URL extrahiert
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// Es kann weitergearbeitet werden
Socket client=null; // der Kunde
BufferedReader IN=null; // der Lesestrom des Kunden
PrintWriter OUT=null; // der Schreib-Stream des Kunden
String réponse=null; // Antwort des Servers
try{
// Verbindung zum Server herstellen
client=new Socket(host,port);
// Die Ein- und Ausgabeströme des Kunden werden erstellt: TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// Anforderung von URL – Senden der Header HTTP
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// die Antwort wird gelesen
while((réponse=IN.readLine())!=null){
// die Antwort wird verarbeitet
System.out.println(réponse);
}//while
// ist der Vorgang abgeschlossen
client.close();
} catch(Exception e){
// die Ausnahme wird behandelt
erreur(e.getMessage(),4);
}//catch
}//main
// Fehleranzeige
public static void erreur(String msg, int exitCode){
// Fehleranzeige
System.err.println(msg);
// Beenden mit Fehler
System.exit(exitCode);
}//Fehler
}//Klasse
Die einzige Neuerung in diesem Programm ist die Verwendung der Klasse URL. Das Programm erhält einen URL (Uniform Resource Locator) oder einen URI (Uniform Resource Identifier) in der Form http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... Die Klasse URL ermöglicht es uns, die Zeichenkette des URL in ihre verschiedenen Elemente zu zerlegen. Ein Objekt URL wird aus der als Parameter übergebenen Zeichenkette URLstring erstellt:
// Gültigkeitsprüfung des URL
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI falsch
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//Abfangen
Wenn die als Parameter übergebene Zeichenfolge URL kein gültiges URL ist (fehlendes Protokoll, fehlender Server usw.), wird eine Ausnahme ausgelöst. Auf diese Weise können wir die Gültigkeit des empfangenen Parameters überprüfen. Sobald das Objekt URL erstellt wurde, haben wir Zugriff auf dessen verschiedene Elemente. Wenn also das Objekt url aus dem vorherigen Code anhand der Zeichenkette
erstellt wurde, ergibt sich Folgendes:
url.getHost() = serveur
url.getPort()=port oder -1, wenn der Port nicht angegeben ist
url.getPath()=cheminPageHTML oder die leere Zeichenfolge, wenn kein Pfad vorhanden ist
url.getQuery()=param1=val1;param2=val2;... oder null, wenn keine Anfrage vorliegt
uri.getProtocol()=http
8.4.6. Web-Client, der Weiterleitungen verwaltet
Der vorherige Web-Client verarbeitet keine mögliche Weiterleitung der von ihm angeforderten URL. 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>java 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:
// importierte Pakete
import java.io.*;
import java.net.*;
import java.util.regex.*;
public class clientweb2{
// fordert ein URL an
// zeigt deren Inhalt auf dem Bildschirm an
public static void main(String[] args){
// Syntax
final String syntaxe="pg URL GET/HEAD";
// Anzahl der Argumente
if(args.length != 2)
erreur(syntaxe,1);
// die angeforderte URI wird notiert
String URLString=args[0];
String commande=args[1].toUpperCase();
// Überprüfung der Gültigkeit des URI
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI ist falsch
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//Abfangen
// Überprüfung der Bestellung
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// Fehlerhafte Bestellung
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// Es kann weitergearbeitet werden
Socket client=null; // der Kunde
BufferedReader IN=null; // der Lesestrom des Kunden
PrintWriter OUT=null; // Schreib-Stream des Clients
String réponse=null; // Antwort des Servers
final int nbRedirsMax=1; // Es wird nicht mehr als eine Weiterleitung akzeptiert
int nbRedirs=0; // Anzahl der laufenden Weiterleitungen
String premièreLigne; // erste Zeile der Antwort
boolean redir=false; // gibt an, ob eine Weiterleitung vorliegt oder nicht
String locationString=""; // die Zeichenfolge URL einer möglichen Weiterleitung
// Regulärer Ausdruck zum Auffinden einer Weiterleitung mit der Zeichenfolge URL
Pattern location=Pattern.compile("^Location: (.+?)$");
// Fehlerbehandlung
try{
// Es können mehrere URL abgefragt werden, falls Umleitungen vorliegen
while(nbRedirs<=nbRedirsMax){
// Die relevanten Informationen werden aus dem URL extrahiert
String protocol=url.getProtocol();
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// Man stellt eine Verbindung zum Server her
client=new Socket(host,port);
// Es werden die Ein- und Ausgangsströme des Kunden TCP erstellt
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// URL wird angefordert – Senden der Header HTTP
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// Die erste Zeile der Antwort wird gelesen
premièreLigne=IN.readLine();
// Bildschirmausgabe
System.out.println(premièreLigne);
// Weiterleitung?
if(premièreLigne.endsWith("302 Object moved")){
// Es liegt eine Umleitung vor
redir=true;
nbRedirs++;
}//if
// die folgenden Header HTTP, bis die leere Zeile gefunden wird, die das Ende der Header signalisiert
boolean locationFound=false;
while(!(réponse=IN.readLine()).equals("")){
// die Antwort wird angezeigt
System.out.println(réponse);
// Wenn eine Weiterleitung vorliegt, wird nach dem Header „Location“ gesucht
if(redir && ! locationFound){
// die Zeile wird mit dem relationalen Ausdruck „location“ verglichen
Matcher résultat=location.matcher(réponse);
if(résultat.find()){
// Wurde ein Treffer gefunden, wird der Umleitungs-URL notiert
locationString=résultat.group(1);
// Es wird vermerkt, dass ein Treffer gefunden wurde
locationFound=true;
}//if
}//if
// folgender Header
}//, während
// folgende Zeilen der Antwort
System.out.println(réponse);
while((réponse=IN.readLine())!=null){
// die Antwort wird angezeigt
System.out.println(réponse);
}//while
// die Verbindung wird geschlossen
client.close();
// Sind wir fertig?
if ( ! locationFound || nbRedirs>nbRedirsMax)
break;
// Es muss eine Weiterleitung durchgeführt werden – die neue wird erstellt URL
URLString=protocol +"://"+host+":"+port+locationString;
url=new URL(URLString);
// Sendungsverfolgung
System.out.println("\n<--Redirection vers l'URL "+URLString+"-->\n");
}//während
} catch(Exception e){
// die Ausnahme wird behandelt
erreur(e.getMessage(),4);
}//catch
}//main
// Fehleranzeige
public static void erreur(String msg, int exitCode){
// Fehleranzeige
System.err.println(msg);
// Beenden mit Fehler
System.exit(exitCode);
}//Fehler
}//Klasse
8.4.7. Steuerberechnungsserver
Wir greifen die Übung IMPOTS wieder auf, die bereits in verschiedenen Formen behandelt wurde. Zur Erinnerung: Die letzte Version sah wie folgt aus:
Es wurde eine Basisklasse „Steuern“ angelegt. Ihre Attribute sind drei Arrays mit Zahlen:
public class impots{
// die für die Steuerberechnung erforderlichen Daten
// stammen aus einer externen Quelle
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// Feld „Hersteller“ leer
protected impots(){}
// Konstruktor
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
Die Klasse „impots“ verfügt über zwei Konstruktoren:
- einen Konstruktor, dem die drei für die Steuerberechnung erforderlichen Daten-Arrays übergeben werden
- einen Konstruktor ohne Parameter, der ausschließlich von untergeordneten Klassen verwendet werden kann
Von dieser Klasse wurde die Klasse „impotsJDBC“ abgeleitet, mit der die drei Arrays „limites“, „coeffR“ und „coeffN“ anhand des Inhalts einer Datenbank gefüllt werden können:
public class impotsJDBC extends impots{
// Hinzufügen eines Konstruktors, der die Erstellung ermöglicht
// die Tabellen „limites“, „coeffr“ und „coeffn“ aus der Tabelle
// Steuerdatenbank
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: Name der Datenbank DSN
// userIMPOTS, mdpIMPOTS: Benutzername/Passwort für den Zugriff auf die Datenbank
Es wurde eine grafische Anwendung geschrieben. Die Anwendung verwendete ein Objekt der Klasse impotsJDBC. Die Anwendung und dieses Objekt befanden sich auf demselben Rechner. Wir beabsichtigen, das Testprogramm und das Objekt impotsJDBC auf unterschiedlichen Rechnern zu installieren. Wir erhalten eine Client-Server-Anwendung, bei der das entfernte Objekt impotsJDBC als Server fungiert. Die neue Klasse heißt ServeurImpots und ist von der Klasse impotsJDBC abgeleitet:
// importierte Pakete
import java.net.*;
import java.io.*;
import java.sql.*;
public class ServeurImpots extends impotsJDBC {
// Attribute
int portEcoute; // der Port, auf dem Client-Anfragen abgehört werden
boolean actif; // Serverstatus
// Hersteller
public ServeurImpots(int portEcoute,String DSNimpots, String USERimpots, String MDPimpots)
throws IOException, SQLException, ClassNotFoundException {
// übergeordneter Konstruktor
super(DSNimpots, USERimpots, MDPimpots);
// der Listening-Port wird vermerkt
this.portEcoute=portEcoute;
// derzeit inaktiv
actif=false;
// erstellt und startet einen Thread zum Einlesen der über die Tastatur eingegebenen Befehle
// Der Server wird anhand dieser Befehle verwaltet
Thread admin=new Thread(){
public void run(){
try{
admin();
}catch (Exception ignored){}
}
};
admin.start();
}//ServeurImpots
Der einzige neue Parameter im Konstruktor ist der Port, auf dem Client-Anfragen abgehört werden. Die übrigen Parameter werden direkt an die Basisklasse impotsJDBC übergeben. Der Steuerserver wird über Tastatureingaben gesteuert. Daher wird ein Thread erstellt, 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 void admin() throws IOException{
// liest die über die Tastatur eingegebenen Server-Verwaltungsbefehle
// in einer Endlosschleife
String commande=null;
BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
while(true){
// fordert zur Eingabe auf
System.out.print("Serveur d'impôts>");
// Befehl einlesen
commande=IN.readLine().trim().toLowerCase();
// Befehl ausführen
if(commande.equals("start")){
// aktiv?
if(actif){
//Fehler
System.out.println("Le serveur est déjà actif");
// Weiter
continue;
}//if
// Listening-Dienst wird erstellt und gestartet
Thread ecoute=new Thread(){
public void run(){
ecoute();
}
};
ecoute.start();
}//if
else if(commande.equals("stop")){
// Ende aller Ausführungsthreads
System.exit(0);
}//if
else {
// Fehler
System.out.println("Commande incorrecte. Utilisez (start,stop)");
}//if
}//while
}//Admin
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 void ecoute(){
// Thread zum Abhören von Client-Anfragen
// Der Listening-Dienst wird erstellt
ServerSocket ecoute=null;
try{
// Der Dienst wird erstellt
ecoute=new ServerSocket(portEcoute);
// Weiterverfolgung
System.out.println("Serveur d'impôts lancé sur le port " + portEcoute);
// Dienstschleife
Socket liaisonClient=null;
while (true){ // Endlosschleife
// Warten auf einen Client
liaisonClient=ecoute.accept();
// Der Dienst wird von einer anderen Aufgabe übernommen
new traiteClientImpots(liaisonClient,this).start();
// Es wird wieder auf Anfragen gewartet
}// Ende der while-Schleife
}catch(Exception ex){
// Der Fehler wird gemeldet
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}//Abhör-Thread
Es handelt sich um einen klassischen TCP-Server, der auf dem Port portEcoute lauscht. Die Anfragen der Clients werden von der Methode run des Threads traiteCientImpots verarbeitet, an dessen Konstruktor zwei Parameter übergeben werden:
- das Socket-Objekt liaisonClient, über das der Client erreicht werden kann
- das Objekt impotsJDBC this, das den Zugriff auf die Methode this.calculer zur Steuerberechnung ermöglicht.
// -------------------------------------------------------
// stellt den Dienst für einen Kunden des Steuerservers bereit
class traiteClientImpots extends Thread{
private Socket liaisonClient; // Verbindung zum Kunden
private BufferedReader IN; // Eingangsdatenfluss
private PrintWriter OUT; // Ausgangsstrom
private impotsJDBC objImpots; // Objekt „Steuer“
// Konstruktor
public traiteClientImpots(Socket liaisonClient,impotsJDBC objImpots){
this.liaisonClient=liaisonClient;
this.objImpots=objImpots;
}//Hersteller
Die Methode run verarbeitet Kundenanfragen. Dabei handelt es sich um Textzeilen, die zwei Formen annehmen können:
- Berechnung verheiratet (j/n) nbEnfants salaireAnnuel
- fincalculs
Form 1 ermöglicht die Berechnung einer Steuer, Form 2 beendet die Verbindung zwischen Client und Server.
// Ausführungsmethode
public void run(){
// erbringt die Dienstleistung für den Kunden
try{
// Eingangsfluss
IN=new BufferedReader(new InputStreamReader(liaisonClient.getInputStream()));
// Ausgabestrom
OUT=new PrintWriter(liaisonClient.getOutputStream(),true);
// Versenden einer Willkommensnachricht an den Kunden
OUT.println("Bienvenue sur le serveur d'impôts");
// Schleife zum Lesen der Anfrage und Schreiben der Antwort
String demande=null;
String[] champs=null; // die Elemente der Anfrage
String commande=null; // Der Befehl des Kunden: Berechnung oder Abschluss der Berechnungen
while ((demande=IN.readLine())!=null){
// Die Anfrage wird in Felder zerlegt
champs=demande.trim().toLowerCase().split("\\s+");
// Zwei akzeptierte Anfragen: Berechnung und Abschluss der Berechnungen
commande=champs[0];
if(! commande.equals("calcul") && ! commande.equals("fincalculs")){
// Kundenfehler
OUT.println("Commande incorrecte. Utilisez (calcul,fincalculs).");
// nächste Bestellung
continue;
}//if
if(commande.equals("calcul")) calculerImpôt(champs);
if(commande.equals("fincalculs")){
// Abschiedsmeldung an den Kunden
OUT.println("Au revoir...");
// Freigabe der Ressourcen
try{ OUT.close();IN.close();liaisonClient.close();}
catch(Exception ex){}
// Ende
return;
}//if
//nächste Anfrage
}//while
}catch (Exception e){
erreur("L'erreur suivante s'est produite ("+e+")",2);
}// Ende try
}// Ende Run
Die Steuerberechnung erfolgt über die Methode calculerImpôt, die als Parameter das Feldarray der vom Kunden gestellten Anfrage erhält. Die Gültigkeit der Anfrage wird überprüft und gegebenenfalls die Steuer berechnet und an den Kunden zurückgesendet.
// Steuerberechnung
public void calculerImpôt(String[] champs){
// bearbeitet den Antrag: Berechnung für Verheiratete nbEnfants salaireAnnuel
// in Felder in der Feldtabelle aufgeschlüsselt
String marié=null;
int nbEnfants=0;
int salaireAnnuel=0;
// Gültigkeit der Argumente
try{
// Es sind mindestens 4 Felder erforderlich
if(champs.length!=4) throw new Exception();
// verheiratet
marié=champs[1];
if (! marié.equals("o") && ! marié.equals("n")) throw new Exception();
// Kinder
nbEnfants=Integer.parseInt(champs[2]);
// Gehalt
salaireAnnuel=Integer.parseInt(champs[3]);
}catch (Exception ignored){
// Formatfehler
OUT.println(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel");
// fertig
return;
}//if
// Die Steuer kann berechnet werden
long impot=objImpots.calculer(marié.equals("o"),nbEnfants,salaireAnnuel);
// die Antwort wird an den Kunden gesendet
OUT.println(""+impot);
}//berechnen
Ein Testprogramm könnte wie folgt aussehen:
// Aufruf: serveurImpots Port dsnImpots userImpots mdpImpots
import java.io.*;
public class testServeurImpots{
public static final String syntaxe="Syntaxe : pg port dsnImpots userImpots mdpImpots";
// Hauptprogramm
public static void main (String[] args){
// Es sind 4 Argumente erforderlich
if(args.length != 4)
erreur(syntaxe,1);
// Der Port muss eine ganze Zahl > 0 sein
int port=0;
boolean erreurPort=false;
Exception E=null;
try{
port=Integer.parseInt(args[0]);
}catch(Exception e){
E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0;
if(erreurPort)
erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// Der Steuer-Server wird erstellt
try{
new ServeurImpots(port,args[1],args[2],args[3]);
}catch(Exception ex){
//Fehler
System.out.println("L'erreur suivante s'est produite : "+ex.getMessage());
}//catch
}//Main
// Anzeige der Fehler
public static void erreur(String msg, int exitCode){
// Fehleranzeige
System.err.println(msg);
// Beenden mit Fehler
System.exit(exitCode);
}//Fehler
}// Ende der Klasse
Wir übergeben die für die Erstellung eines Objekts ServeurImpots erforderlichen Daten an das Testprogramm, und dieses erstellt daraufhin das Objekt.
Versuchen wir einen ersten Lauf:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
Der Befehl
erstellt ein Objekt ServeurImpots, das noch nicht auf Client-Anfragen wartet. Erst 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>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
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:
dos>java testServeurImpots 124 mysql-dbimpots admimpots mdpimpots
Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
8.5. Übungen
8.5.1. Übung 1 – Generischer grafischer Client TCP
8.5.1.1. Vorstellung der Anwendung
Wir wollen ein Programm erstellen, das über das Internet mit den wichtigsten TCP-Diensten kommunizieren kann. Wir nennen es einen generischen TCP-Client. Wenn man diese Anwendung verstanden hat, stellt man fest, dass sich alle TCP-Clients ähneln. Das Programmfenster sieht wie folgt aus:

Die verschiedenen Steuerelemente haben folgende Bedeutung:
Nr. | Name | Typ | Funktion |
1 | TxtRemoteHost | JTextField | Name des Rechners, der den gewünschten Dienst anbietet |
2 | TxtPort | JTextField | Port des angeforderten Dienstes |
3 | TxtSend | JTextField | Text der Nachricht, die vom Client an den Server gesendet wird |
4 | OptRCLF OptLF | JCheckBox | Schaltflächen zur Festlegung, wie die Zeilen im Client-Server-Dialog enden RCLF: Wagenrücklauf (#13) + Zeilenumbruch (#10) LF: Zeilenumbruch (#10) |
5 | LstSuivi | JList | zeigt Meldungen zum Kommunikationsstatus zwischen Client und Server an |
6 | LstDialogue | JList | zeigt die vom Client (->) und vom Server (<-) ausgetauschten Meldungen an |
7 | CmdAnnuler | JButton | ausgeblendet – befindet sich unterhalb der Dialogliste – wird angezeigt, wenn die Verbindung hergestellt ist, und ermöglicht es, diese zu unterbrechen, falls der Server nicht antwortet |
Folgende Menüoptionen stehen zur Verfügung:
Option | Unteroptionen | Rolle |
Verbindung | Verbinden | verbindet den Client mit dem Server |
Trennen | beendet die Verbindung | |
Beenden | Beendet das Programm | |
Meldungen | Senden | Sendet die Nachricht des Steuerelements TxtSend an den Server |
RazSuivi | Löscht die Liste LstSuivi | |
RazDialogue | Löscht die Liste LstDialogue | |
Autor | Zeigt ein Copyright-Fenster an |
8.5.1.2. FONCTIONNEMENT DE APPLICATION
Wenn das Hauptblatt der Anwendung geladen wird, finden folgende Aktionen statt:
- Das Blatt wird auf dem Bildschirm zentriert
- nur die Menüoptionen Connexion/Quitter und Auteur sind aktiv
- Die Schaltfläche Annuler ist ausgeblendet
- Die Listen LstSuivi und LstDialogue sind leer
Diese Option ist nur verfügbar, wenn die Felder „Remote-Host“ und „Portnummer“ ausgefüllt sind und derzeit keine Verbindung besteht. Ein Klick auf diese Option führt zu folgenden Vorgängen:
- Die Gültigkeit des Ports wird überprüft: Es muss sich um eine ganze Zahl > 0 handeln
- Es wird ein Thread gestartet, um die Verbindung zum Server herzustellen
- Die Schaltfläche „Annuler“ wird angezeigt, damit der Benutzer die laufende Verbindung unterbrechen kann
- Alle Menüoptionen werden deaktiviert, mit Ausnahme von „Quitter“ und „Auteur“
Die Verbindung kann auf verschiedene Arten beendet werden:
- Der Benutzer hat auf die Schaltfläche „Annuler“ geklickt: Der Verbindungsthread wird beendet und das Menü in seinen Ausgangszustand zurückversetzt. In der Protokollierung wird vermerkt, dass die Verbindung vom Benutzer beendet wurde.
- Die Verbindung wird mit einem Fehler beendet: Es wird wie zuvor verfahren, und zusätzlich wird im Protokoll die Fehlerursache angegeben.
- Die Verbindung wird erfolgreich beendet: Die Schaltfläche Annuler wird entfernt, im Protokoll wird vermerkt, dass die Verbindung hergestellt wurde, das Menü RazSuivi wird freigegeben, das Menü Connecter wird gesperrt, das Menü Déconnecter wird freigegeben
Diese Option ist nur verfügbar, wenn eine Verbindung zum Server besteht. Wenn sie aktiviert wird, wird die Verbindung zum Server beendet und das Menü in seinen Ausgangszustand zurückversetzt. In der Protokollierung wird vermerkt, dass die Verbindung vom Client beendet wurde.
Diese Option beendet eine eventuell aktive Verbindung zum Server und schließt die Anwendung.
Diese Option ist nur verfügbar, wenn die folgenden Bedingungen erfüllt sind:
-
Die Verbindung zum Server wurde hergestellt
-
es gibt eine zu versendende Nachricht
Sind diese Bedingungen erfüllt, wird der im Feld TxtSend (3) enthaltene Text an den Server gesendet, ergänzt um die Sequenz RCLF, sofern die Option RCLF aktiviert wurde; andernfalls um die Sequenz LF. Ein eventueller Fehler beim Senden wird in der Nachverfolgungsliste angezeigt.
Leeren jeweils die Listen LstSuivi und LstDialogue. Diese Optionen sind deaktiviert, wenn die entsprechenden Listen leer sind.
Diese Schaltfläche am unteren Rand des Formulars wird nur angezeigt, wenn der Client gerade eine Verbindung zum Server herstellt. Diese Verbindung kann fehlschlagen, weil der Server nicht oder nicht richtig antwortet. Die Schaltfläche Annuler bietet dem Benutzer dann die Möglichkeit, die Verbindungsanfrage abzubrechen.
Die Liste LstSuivi (5) verfolgt den Verbindungsstatus. Sie zeigt die wichtigsten Zeitpunkte der Verbindung an:
-
ihre Eröffnung durch den Client
-
das Schließen der Verbindung durch den Server oder den Client
-
alle Fehler, die auftreten können, solange die Verbindung aktiv ist
Die Liste LstDialogue (6) verfolgt den Dialog, der zwischen dem Client und dem Server stattfindet. Ein Thread überwacht im Hintergrund, was am Kommunikations-Socket des Clients geschieht, und zeigt dies in Liste 6 an.
Dieses Menü öffnet ein sogenanntes Copyright-Fenster:

Verbindungsfehler werden in der Überwachungsliste 6 angezeigt, Fehler im Zusammenhang mit dem Client-Server-Dialog in der Dialogliste 7. Bei einem Verbindungsfehler wird der Client-Server-Dialog geschlossen und das Formular in seinen Ausgangszustand zurückversetzt, sodass es für eine neue Verbindung bereit ist.
8.5.1.3. TRAVAIL A FAIRE
Führen Sie die zuvor beschriebene Arbeit in zwei Formen durch:
- Standalone-Anwendung
- Applet
8.5.2. Übung 2 – Ein Ressourcenserver
8.5.2.1. INTRODUCTION
Eine Einrichtung verfügt über mehrere leistungsstarke Rechenserver, die über das Internet zugänglich sind. Jeder Rechner, der diese Rechen Dienste nutzen möchte, sendet eine Datendatei an Port 756 eines der Server. Diese Datei enthält verschiedene Informationen: Benutzername, Passwort, Befehle zur Angabe der gewünschten Rechenart sowie die Daten, auf denen die Berechnung durchgeführt werden soll. Ist die Datendatei korrekt, verarbeitet der ausgewählte Rechenserver sie und sendet die Ergebnisse in Form einer Textdatei an den Client zurück.
Eine solche Organisation bietet zahlreiche Vorteile:
- Jeder Client-Typ (PC, Mac, Unix, ...) kann diesen Dienst nutzen
- Der Kunde kann sich an einem beliebigen Ort im Internet befinden
- Die Rechenressourcen sind optimiert: Es sind nur wenige leistungsstarke Rechner erforderlich. So kann auch eine kleine Organisation ohne eigene Rechenressourcen diesen Dienst gegen einen finanziellen Beitrag nutzen, der sich nach der in Anspruch genommenen Rechenzeit richtet.
Trotz der Leistungsfähigkeit der Rechner kann eine Berechnung manchmal mehrere Stunden dauern: Der Server steht dann für andere Clients nicht zur Verfügung. Für einen Client stellt sich somit das Problem, einen verfügbaren Rechenserver zu finden. Zu diesem Zweck wird ein „Rechenressourcenmanager“ verwendet, der im Folgenden als GRC-Server bezeichnet wird. Dieser Dienst läuft auf einem einzigen Rechner und arbeitet im TCP-Modus auf Port 864. An ihn wendet sich ein Client, der Zugriff auf einen Rechenserver wünscht. Der Server GRC, der über die vollständige Liste der Rechenserver verfügt, antwortet ihm, indem er den Namen eines derzeit inaktiven Servers übermittelt. Der Client muss dann nur noch seine Daten an den ihm zugewiesenen Server senden.
Wir wollen nun den Server GRC programmieren.
8.5.2.2. Der INTERFACE VISUELLE
Die visuelle Benutzeroberfläche sieht wie folgt aus:

Die Benutzeroberfläche zeigt zwei Serverlisten an:
- links die Liste der inaktiven und somit für Berechnungen verfügbaren Server
- rechts die Liste der Server, die gerade mit den Berechnungen eines Kunden beschäftigt sind.
Die Menüstruktur sieht wie folgt aus:
Hauptmenü | Untermenü | Rolle |
Dienst | Starten | Startet den TCP-Dienst auf Port 864 |
Beenden | Beendet den Dienst | |
Beenden | Beendet die Anwendung | |
Autor | Urheberrechtshinweise |
Die Struktur der Steuerelemente im Formular ist wie folgt:
Name | Typ | Rolle |
listLibres | JList | Liste der freien Server |
listOccupés | JList | Liste der belegten Server |
8.5.2.3. FONCTIONNEMENT DE APPLICATION
Beim Laden der Anwendung wird die Liste listLibres mit den Namen der vom GRC verwalteten Rechenserver gefüllt. Diese sind in einer als Parameter übergebenen Serverdatei definiert. Diese Datei enthält eine Liste mit Servernamen, wobei pro Zeile ein Name steht, und wird daher zum Ausfüllen der Liste listLibres verwendet. Das Menü „Starten“ ist freigegeben, das Menü „Beenden“ ist gesperrt.
Diese Option
- startet den Listening-Dienst auf Port 864 des Rechners
- sperrt das Menü „Starten“
- aktiviert das Menü „Beenden“
Diese Option beendet den Dienst:
- Die Liste der belegten Server wird geleert
- die Liste der freien Server wird mit dem Inhalt der Datei „Server“ gefüllt
- Das Menü „Starten“ wird freigegeben
- das Menü „Beenden“ wird gesperrt
Die Anwendung wird beendet.
Der Client-Server-Dialog erfolgt durch den Austausch von Textzeilen, die mit der Sequenz RCLF enden. Der Server GRC erkennt zwei Befehle: getserveur und finservice. Im Folgenden wird die Funktion dieser beiden Befehle näher erläutert:
- 1-getserveur
Der Client fragt an, ob ein Rechenserver für ihn verfügbar ist.
Der Server GRC wählt dann den ersten Server aus seiner Liste der freien Server aus und sendet dessen Namen in folgender Form an den Client zurück:
Außerdem trägt er den dem Kunden zugewiesenen Server in die Liste der belegten Server ein, und zwar in der Form:
wie das folgende Beispiel zeigt, in dem der Server calcul1.istia.univ-angers.fr gerade den Client mit der Adresse IP 193.52.43.5 bedient:

Ein Client kann keinen „getserver“-Befehl senden, wenn ihm bereits ein Rechenserver zugewiesen wurde. Bevor der Server GRC dem Client antwortet, überprüft er daher, ob die Client-Adresse IP nicht bereits in der Liste der belegten Server aufgeführt ist. Ist dies der Fall, antwortet der Server GRC:
Schließlich gibt es noch den Fall, dass kein Rechenserver verfügbar ist: Die Liste der freien Server ist leer. In diesem Fall antwortet der Server GRC:
In jedem Fall beendet der Server GRC nach der Antwort an den Client die Verbindung zu diesem, um andere Clients bedienen zu können.
- 2-finservice
Der Client teilt mit, dass er den bisher genutzten Rechenserver nicht mehr benötigt.
Der Server GRC überprüft zunächst, ob es sich bei dem Client tatsächlich um einen Client handelt, den er bedient hat. Dazu überprüft er, ob die Adresse IP des Clients in der Liste der belegten Server aufgeführt ist. Ist dies nicht der Fall, antwortet der Server GRC:
Wird der Client erkannt, antwortet der Server GRC ihm:
und verschiebt den diesem Client zugewiesenen Rechenserver in die Liste der freien Server. Um das vorherige Beispiel fortzusetzen: Wenn der Client den Befehl „finservice“ sendet, lautet die Anzeige des Servers GRC:

Nach dem Senden der Antwort, unabhängig von deren Inhalt, beendet der Server GRC die Verbindung.
8.5.2.4. TRAVAIL an FAIRE
Schreiben Sie die Anwendung als eigenständiges Programm, das beispielsweise mit einem telnet-Client oder mit dem generischen TCP-Client aus der vorherigen Übung getestet werden kann.
8.5.3. Übung 3 – Ein SMTP-Client
8.5.3.1. INTRODUCTION
Wir möchten hier einen Client für den Dienst SMTP (SendMail Transfer Protocol) erstellen, der das Versenden von E-Mails ermöglicht. Unter Unix oder Windows ist das Programm telnet ein Client, der mit dem Protokoll tcp arbeitet. Es kann mit jedem tcp-Dienst „kommunizieren“, der Befehle im Textformat akzeptiert, die mit der Sequenz RCLF enden, d. h. mit den Codezeichen ASCII 13 und 10. Hier ein Beispiel für eine Kommunikation mit dem Dienst smtp zum Versenden von E-Mails:
// Antwort des SMTP-Servers
Trying 193.52.43.2...
Connected to istia.univ-angers.fr.
Escape character is '^]'.
220-Istia.Istia.Univ-Angers.fr Sendmail 8.6.10/8.6.9 ready at Tue, 16 Jan 1996 07:53:12 +0100
220 ESMTP spoken here
// Kommentare --------------
Das Programm telnet kann jeden Dienst über die folgende Syntax aufrufen
telnet machine_service port_service
Der Client-Server-Austausch erfolgt über Textzeilen, die mit der Sequenz RCLF enden.
Die Antworten des Dienstes smtp haben folgende Form:
Nachrichtennummer oder
Nachrichtennummer
Der SMTP-Server kann mehrere Antwortzeilen senden. Die letzte Zeile der Antwort wird durch eine Nummer gefolgt von einem Leerzeichen gekennzeichnet, während bei den vorhergehenden Zeilen der Antwort auf die Nummer ein Bindestrich (-) folgt.
Eine Nummer größer oder gleich 500 weist auf eine Fehlermeldung hin.
// Ende der Kommentare
// Antwort des SMTP-Servers
214-Commands:
214- HELO EHLO MAIL RCPT DATA
214- RSET NOOP QUIT HELP VRFY
214- EXPN VERB
214-For more info use "HELP <topic>".
214-To report bugs in the implementation send email to
214- sendmail@CS.Berkeley.EDU.
214-For local information send email to Postmaster at your site.
214 End of HELP info
// Kommentare ---------
Der Befehl „mail“ hat folgende Syntax:
mail from: E-Mail-Adresse des Absenders der Nachricht
// Ende der Kommentare
// Antwort des SMTP-Servers
// Kommentare
Der Server smtp führt keine Gültigkeitsprüfung der Absenderadresse durch: Er übernimmt sie so, wie sie ihm übermittelt wurde
// Ende der Kommentare
// Kommentare ---------
Der Befehl „rcpt“ hat folgende Syntax:
rcpt to: E-Mail-Adresse des Empfängers der Nachricht
Wenn es sich bei der E-Mail-Adresse um eine Adresse des Rechners handelt, auf dem der Server smtp läuft, überprüft dieser, ob die Adresse tatsächlich existiert; andernfalls erfolgt keine Überprüfung. Wenn eine Überprüfung stattgefunden hat und ein Fehler festgestellt wurde, wird dieser mit einer Nummer >= 500 gemeldet.
Es können beliebig viele „rcpt to“-Befehle ausgegeben werden: Auf diese Weise lässt sich eine Nachricht an mehrere Personen senden.
// Ende der Kommentare
// Antwort des SMTP-Servers
// Kommentare ---------
Der Befehl „data“ hat folgende Syntax:
data
Zeile1
Zeile2
...
.
Darauf folgen die Textzeilen, aus denen die Nachricht besteht, wobei diese mit einer Zeile enden muss, die ausschließlich das Zeichen „Punkt“ enthält.
Die Nachricht wird dann an den durch den Befehl rcpt angegebenen Empfänger gesendet.
// Ende der Kommentare
// Antwort des SMTP-Servers
// per Tastatur eingegebener Nachrichtentext
subject: essai smtp
essai smtp a partir de telnet
.
// Kommentare
In den Textzeilen des Befehls „data“ kann eine Zeile „subject:“ eingefügt werden, um den Betreff der E-Mail anzugeben. Auf diese Zeile muss eine Leerzeile folgen.
// Antwort des SMTP-Servers
// Kommentare
Der Befehl „quit“ beendet die Verbindung zum Dienst smtp
// Ende der Kommentare
// Antwort des SMTP-Servers
8.5.3.2. INTERFACE VISUELLE
Wir schlagen vor, ein Programm mit der folgenden grafischen Benutzeroberfläche zu erstellen:

Die Steuerelemente haben folgende Funktionen:
Nummer | Typ | Funktion |
1 | JTextField | Durch Kommas getrennte E-Mail-Adressen |
2 | JTextField | Betreff der Nachricht |
3 | JTextField | Durch Kommas getrennte E-Mail-Adressen |
4 | JTextField | Durch Kommas getrennte E-Mail-Adressen |
5 | JTextArea | Nachrichtentext |
6 | JList | Beobachtungsliste |
7 | JList | Dialogliste |
8 | JButton | Die Schaltfläche „Abbrechen“ wird nicht angezeigt und erscheint, wenn der Client eine Verbindung zum Server SMTP anfordert. Ermöglicht es dem Benutzer, diese Anfrage abzubrechen, falls der Server nicht antwortet. |
8.5.3.3. LES MENUS
Die Menüstruktur der Anwendung ist wie folgt:
Hauptmenü | Untermenü | Rolle |
E-Mail | ||
Senden | Sende die Nachricht von Steuerung 5 | |
Beenden | Die App beenden | |
Optionen | ||
Verfolgung ausblenden | Macht das Steuerelement 6 unsichtbar | |
Tracking zurücksetzen | Leert die Beobachtungsliste 6 | |
Dialogfeld ausblenden | Macht die Dialogliste unsichtbar 7 | |
Dialog löschen | Bezieht die Dialogliste zurück 7 | |
Konfigurieren | Ermöglicht es dem Benutzer, Folgendes anzugeben - die Adresse des vom Programm verwendeten SMTP-Servers - seine E-Mail-Adresse | |
Speichern... | Speichert die vorherige Konfiguration in einer .ini-Datei | |
Autor | Copyright-Informationen |
8.5.3.4. FONCTIONNEMENT DE APPLICATION
Dieses Menü öffnet das folgende Fenster:

Beide Felder müssen ausgefüllt werden, damit die Schaltfläche OK aktiv ist. Beide Angaben müssen in globalen Variablen gespeichert werden, damit sie für andere Module verfügbar sind.
Diese Option ist nur verfügbar, wenn die folgenden Bedingungen erfüllt sind:
- Die Konfiguration wurde vorgenommen
- es gibt eine zu versendende Nachricht
- es gibt einen Betreff
- in den Feldern 1, 3 und 4 ist mindestens ein Empfänger angegeben
Sind diese Bedingungen erfüllt, verläuft die Abfolge der Ereignisse wie folgt:
- Das Formular wird in einen Zustand versetzt, in dem alle Aktionen, die den Client-Server-Dialog beeinträchtigen könnten, gesperrt sind
- Es wird eine Verbindung zum Port 25 des in der Konfiguration angegebenen Servers hergestellt
- Der Client kommuniziert anschließend gemäß dem oben beschriebenen Protokoll mit dem SMTP-Server
- Die „Mail From“-Adresse verwendet die in der Konfiguration angegebene E-Mail-Adresse des Absenders
- Das Feld „rcpt to:“ wird für jede der E-Mail-Adressen verwendet, die in den Feldern 1, 3 und 4 gefunden wurden
- In den Zeilen, die nach dem Befehl data gesendet werden, finden sich folgende Texte:
- eine Zeile Subject: – Betrefftext der Prüfung 2
- eine Zeile Cc:: Adressen der Prüfung 3
- eine Zeile Bcc:: Adressen der Prüfung 4
- der Text der Meldung der Prüfung 5
- der Endpunkt
Diese Schaltfläche am unteren Rand des Formulars wird nur angezeigt, wenn der Client gerade eine Verbindung zum Server smtp herstellt. Diese Verbindung kann fehlschlagen, weil der Server smtp nicht oder fehlerhaft antwortet. Die Schaltfläche „Annuler“ bietet dem Benutzer dann die Möglichkeit, die Verbindungsanfrage abzubrechen.
Die Liste (6) verfolgt den Verbindungsstatus. Sie zeigt die wichtigsten Zeitpunkte der Verbindung an:
- die Eröffnung durch den Client
- die Schließung durch den Server oder den Client
- alle Verbindungsfehler
Die Liste (7) protokolliert den Dialog smtp, der zwischen dem Client und dem Server stattfindet.
Diese beiden Listen sind mit Menüoptionen verknüpft:
Wiedergabeliste ausblenden | Blendet die Merklisten-Liste 6 sowie die darüber befindliche Beschriftung aus. Wenn die Höhe, die diese beiden Steuerelemente einnehmen, H beträgt, werden alle darunter liegenden Steuerelemente um eine Höhe von H nach oben verschoben und die Gesamtgröße des Formulars um H verringert. Außerdem macht „Verfolgen ausblenden“ die Option RazSuivi darunter unsichtbar. |
Verfolgung zurücksetzen | Leert die Nachverfolgungsliste 6 |
Dialogfeld ausblenden | Blendet die Dialogliste 7, die darüber befindliche Beschriftung sowie die darunter liegende Menüoption RazDialogue aus. Wie bei „Verfolgen ausblenden“ wird die Position der darunter liegenden Steuerelemente (möglicherweise die Schaltfläche Annuler) neu berechnet und die Fenstergröße verringert. |
Dialogfeld „Raz“ | Leert die Dialogliste 7 |
Dieses Menü öffnet ein sogenanntes Copyright-Fenster:

Verbindungsfehler werden in der Überwachungsliste 6 gemeldet, Fehler im Zusammenhang mit dem Client/Server-Dialog in der Dialogliste 7. Bei einem Fehler wird der Benutzer durch eine Fehlermeldung benachrichtigt, und die Liste mit der Fehlerursache wird angezeigt, falls sie zuvor ausgeblendet war. Außerdem wird der Client-Server-Dialog geschlossen und das Formular in seinen ursprünglichen Zustand zurückversetzt.
8.5.3.5. GESTION von UN FICHIER DE CONFIGURATION
Es ist wünschenswert, dass der Benutzer die Software nicht bei jeder Nutzung neu konfigurieren muss. Ist dazu die Option „Optionen/Konfiguration beim Beenden speichern“ aktiviert, werden beim Schließen des Programms die beiden über die Option Options/Configurer erfassten Informationen sowie der Status der beiden Überwachungslisten in einer Datei namens sendmail.ini gespeichert, die sich im selben Verzeichnis wie die .exe-Datei des Programms befindet. Diese Datei hat das folgende Format:
SmtpServer=shiva.istia.univ-angers.fr
ReplyAddress=serge.tahe@istia.univ-angers.fr
Suivi=0
Dialogue=1
Die Zeilen SmtpServer und ReplyAddress enthalten die beiden Informationen, die über die Option „Optionen/Konfigurieren“ erfasst wurden. Die Zeilen Suivi und Dialogue geben den Status der Listen „Verfolgung“ und „Dialog“ an: 1 (vorhanden), 0 (nicht vorhanden).
Beim Laden des Programms wird die Datei sendmail.ini gelesen, sofern sie vorhanden ist, und das Formular wird entsprechend konfiguriert. Wenn die Datei sendmail.ini nicht vorhanden ist, wird so verfahren, als ob sie vorhanden wäre:
Wenn die Datei „sendmail.ini“ zwar existiert, aber unvollständig ist (fehlende Zeilen), wird die fehlende Zeile durch die entsprechende Zeile aus der obigen Liste ersetzt. Da also die Zeile „Suivi=...“ fehlt, wird so verfahren, als ob wir „Suivi=1“ hätten.
Alle Zeilen, die nicht der Vorlage entsprechen:
werden ignoriert, ebenso wie diejenigen, bei denen das Schlüsselwort ungültig ist. Das Schlüsselwort kann in Groß- oder Kleinbuchstaben geschrieben sein: Das spielt keine Rolle.
In der Option Options/Configurer werden die derzeit aktiven Werte SmtpServer und ReplyAddress angezeigt. Der Benutzer kann diese dann nach Belieben ändern.
8.5.3.6. TRAVAIL A FAIRE
Führen Sie die zuvor beschriebene Arbeit durch. Es wird empfohlen, die Bearbeitung der Konfigurationsdatei als Letztes durchzuführen.
8.5.4. Übung 4 – Client POPPASS
8.5.4.1. Introduction
Es soll ein Client mit der Bezeichnung TCP erstellt werden, der mit dem Server POPPASSD kommunizieren kann, der auf Port 106 läuft. Dieser Dienst ermöglicht es, das Passwort auf einem Rechner mit der Bezeichnung UNIX zu ändern. Das Protokoll für die Client-Server-Kommunikation lautet wie folgt:
1 - Die Kommunikation erfolgt durch den Austausch von Nachrichten, die mit der Sequenz RCLF enden
2 - Der Client sendet Befehle an den Server
- Der Server antwortet mit Nachrichten, die mit dreistelligen Zahlen beginnen: XXX. Wenn XXX=200 ist, wurde der Befehl korrekt ausgeführt, andernfalls ist ein Fehler aufgetreten.
3 - Die Abfolge des Datenaustauschs ist wie folgt:
- Der Server antwortet mit einer Begrüßungsnachricht
- Der Server fordert das Passwort an, wenn die Anmeldung akzeptiert wird; andernfalls gibt er eine Fehlermeldung aus
- Der Server fordert das neue Passwort an, wenn das Passwort akzeptiert wird; andernfalls wird eine Fehlermeldung ausgegeben
- Der Server antwortet mit der Bestätigung, dass das neue Passwort akzeptiert wurde; andernfalls wird eine Fehlermeldung ausgegeben
- Der Server sendet eine Abschlussmeldung und schließt die Verbindung
8.5.4.2. Das Formular des Clients

Die verschiedenen Steuerelemente haben folgende Bedeutung:
Nr. | Name | Typ | Funktion |
1 | txtRemoteHost | JTextField | Servername |
2 | txtLogin | JTextField | Benutzername |
3 | txtMdp | JTextField | Benutzerkennwort |
4 | txtNewMdp | JTextField | Neues Passwort des Benutzers |
5 | txtConfirmation | JTextField | Bestätigung des neuen Passworts |
6 | lstSuivi | JList | Meldungen zur Verbindungsüberwachung |
7 | lstDialogue | JList | Meldungen aus dem Client/Server-Dialog |
10 | cmdAnnuler | JButton | nicht dargestellt – Schaltfläche, die angezeigt wird, wenn eine Verbindung zum Server hergestellt wird. Ermöglicht das Beenden der Verbindung. |
8.5.4.3. Die Menüs
Titel | Name des Steuerelements | Funktion |
Verbindung | mnuconnexion | |
Verbinden | mnuconnecter | stellt die Verbindung zum Server her |
Beenden | mnuQuitter | beendet die Anwendung |
Meldungen | mnuMessages | |
RazSuivi | mnuRazSuivi | Löscht die Liste lstSuivi |
RazDialogue | mnuRazDialogue | Löscht die Liste lstDialogue |
Autor | mnuAuteur | Zeigt das Copyright-Fenster an |
8.5.4.4. Funktionsweise der Anwendung
Wenn das Hauptfenster der Anwendung geladen wird, finden folgende Aktionen statt:
- Das Blatt wird auf dem Bildschirm zentriert
- nur die Menüoptionen Connexion/Quitter und Auteur sind aktiv
- die Schaltfläche Annuler wird ausgeblendet
- die Listen LstSuivi und LstDialogue sind leer
Diese Option ist nur verfügbar, wenn die Felder 1 bis 5 ausgefüllt wurden. Ein Klick auf diese Option führt zu folgenden Vorgängen:
- Es wird ein Thread gestartet, um die Verbindung zum Server herzustellen
- Die Schaltfläche Annuler wird angezeigt, damit der Benutzer die laufende Verbindung unterbrechen kann
- Alle Menüoptionen werden deaktiviert, mit Ausnahme von Quitter und Auteur
Die Abfolge der Ereignisse ist anschließend wie folgt:
- Der Benutzer hat auf die Schaltfläche „Annuler“ geklickt: Der Verbindungsthread wird beendet und das Menü in seinen Ausgangszustand zurückversetzt. In der Protokollierung wird vermerkt, dass die Verbindung vom Benutzer beendet wurde.
- Die Verbindungsanfrage wird vom Server akzeptiert. Anschließend wird der Dialog mit dem Server zur Änderung des Passworts aufgenommen. Der Datenaustausch dieses Dialogs wird in der Liste LstDialogue protokolliert. Nach Beendigung des Dialogs wird die Verbindung zum Server geschlossen und das Formularmenü in seinen Ausgangszustand zurückversetzt.
- Solange der Dialog aktiv ist, bleibt die Schaltfläche „Abbrechen“ sichtbar, damit der Benutzer die Verbindung bei Bedarf beenden kann.
- Tritt während der Kommunikation ein Fehler auf, wird die Verbindung geschlossen und die Fehlerursache in der Protokoll-Liste LstSuivi angezeigt.
Diese Option beendet eine eventuell aktive Verbindung zum Server und schließt die Anwendung.
Leeren jeweils die Listen LstSuivi und LstDialogue. Diese Optionen sind deaktiviert, wenn die entsprechenden Listen leer sind.
Diese Schaltfläche am unteren Rand des Formulars wird nur angezeigt, wenn der Kunde gerade eine Verbindung zum Server herstellt oder bereits mit dem Server verbunden ist. Die Schaltfläche Annuler bietet dem Benutzer die Möglichkeit, die Kommunikation mit dem Server zu unterbrechen.
Die Liste LstSuivi (5) verfolgt den Verbindungsverlauf. Sie zeigt die wichtigsten Zeitpunkte der Verbindung an:
-
ihre Eröffnung durch den Client
-
das Schließen der Verbindung durch den Server oder den Client
-
alle Fehler, die auftreten können, solange die Verbindung aktiv ist
Die Liste LstDialogue (6) verfolgt den Dialog, der zwischen dem Client und dem Server stattfindet.
Dieses Menü öffnet ein sogenanntes Copyright-Fenster:

Kommunikationsfehler werden in der Überwachungsliste 6 angezeigt, Fehler im Zusammenhang mit dem Client-Server-Dialog in der Dialogliste 7. Bei einem Verbindungsfehler wird der Client-Server-Dialog geschlossen und das Formular in seinen Ausgangszustand zurückversetzt, sodass eine neue Verbindung hergestellt werden kann.
8.5.4.5. TRAVAIL A FAIRE
Führen Sie die zuvor beschriebene Aufgabe zunächst als eigenständige Anwendung und anschließend als Applet durch.