![]() |
|
|||||
Häufig wird der erste Konstruktor für Client-Programme verwendet, die anderen beiden nutzt der Server. Der Unterschied in den Konstruktoren liegt darin, an welche Ports und Server die DatagramSocket-Objekte gebunden sind. Für den Client ist dies nicht so interessant, da er häufig als Absender einen beliebigen Port nutzen kann. Läuft ein Paket zum Server, kann dieser immer anhand der gespeicherten Adresse eine Rückantwort schicken. Wir werden das auch an den Beispielen sehen, wo wir erst ein leeres Paket als Anfrage schicken und dann den Server über uns informieren. Einen beliebigen Port nimmt der erste Konstruktor, denn der bedeutet, dass jeder Port zur Kommunikation in Richtung Server verwendet werden kann. Nur ein Client muss wissen, auf welchem Port ein Server seinen Dienst bereitstellt. Die Port-Adresse auf der Client-Seite festzusetzen ist nur dann wichtig, wenn hinter einer Firewall operiert wird.
16.14.2 Datagramme und die Klasse DatagramPacket
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Wenn wir empfangen wollen, müssen wir warten, bis ein Paket eintrifft. Das geschieht mit der DatagramSocket-Methode receive(DatagramPacket). Die Methode ist vergleichbar mit der accept()-Methode der Klasse ServerSocket, nur dass accept() ein Socket-Objekt zurückgibt und receive() die Daten in dem im Parameter übergebenen Objekt ablegt. Mit den Methoden getPort() und getAddress() können wir herausfinden, woher das Paket kam, wer also der Sender war. Mit getData() bekommen wir die Daten als Bytefeld, und getLength() liefert die Länge. Ist das empfangene Paket größer als unser Puffer, wird das Feld nur bis zur maximalen Größe gefüllt.
Das folgende Programm implementiert einen horchenden Server, der noch nicht auf Pakete antwortet. Es empfängt still und gibt die Informationen über das empfangene Paket aus.
import java.net.*; import java.util.*; public class UDPServer { public static void main( String args[] ) { try { DatagramSocket socket = new DatagramSocket( 4711 ); DatagramPacket packet; while ( true ) { // Auf Anfrage warten packet = new DatagramPacket( new byte[1024], 1024 ); socket.receive( packet ); // Empfänger auslesen InetAddress address = packet.getAddress(); int port = packet.getPort(); int len = packet.getLength(); byte data[] = packet.getData(); System.out.println( "Anfrage von " + address + " vom Port " + port + " Länge " + len + "\n" + new String( data, 0, len ) ); } } catch ( Exception e ) { System.out.println( e ); } } }
Wenn wir ein Paket senden wollen, dann müssen wir einem DatagramPacket auch noch sagen, wohin die Reise geht, das heißt, der Port und die IP-Adresse des entfernten Rechners sind anzugeben. Der Empfänger wird durch ein InetAddress-Objekt repräsentiert, der Konstruktor ist leider nicht mit einem String-Objekt überladen, was sicherlich nützlich wäre. Es gibt aber einen speziellen Konstruktor, der die Inet-Adresse und den Port direkt entgegennimmt.
Folgende Zeilen erzeugen ein DatagramPacket-Objekt mit einem Bytefeld für den Empfänger und senden es gleich:
InetAddress ia; ia = InetAddress.getByName( "www.reich-und-schoen-waere.toll" ); int port = 4711; String s = "Wer andere links liegen lässt, steht rechts."; byte data[] = s.getBytes(); packet = new DatagramPacket( data, data.length, ia, port ); DatagramSocket toSocket = new DatagramSocket(); toSocket.send( packet );
Zusätzlich zum Bytefeld geben wir noch die Anzahl der Bytes an, die gesendet werden sollen. Dies erinnert an C-Stil und ist eigentlich unnötig, da in Java das Bytefeld in der Länge abgefragt werden kann, und hier fast immer data.length passt. Doch so sind wir etwas flexibler. Wenn wir Strings übermitteln, was häufig vorkommt, bietet sich getBytes() zur Umwandlung an. Eine andere Möglichkeit zur Umwandlung einer Zeichenkette in ein Bytefeld ist folgende:
String s = "Gebt einem Brandstifter nie euren Zündschlüssel." byte data[] = new byte [ s.length() ]; s.getBytes( 0, data.length, data, 0 );
Das DatagramPaket ist auch nachträglich veränderbar und kann mit Methoden angepasst und auch ausgelesen werden:
class java.net.DatagramSocket |
| InetAddress getAddress() Hier müssen wir unterscheiden, ob das Paket hereinkommend oder ausgehend ist. Für ein hereinkommendes DatagramPacket liefert die Methode die Adresse, von der das Paket kam. Für ein ausgehendes Paket liefert getAddress() die Adresse, an die das Paket geht. |
| public int getPort() Für ein hereinkommendes Paket liefert es die Port-Nummer vom Sender. Für ein ausgehendes Paket liefert getPort() den Port, an den das Datagram geht. |
Das folgende Programm zeigt ein zu sendendes Paket, und wir können die abgelegten Informationen wieder auslesen.
Listing 16.19 DatagramPacketEntries.java
import java.net.*; import java.util.*; public class DatagramPacketEntries { public static void main( String args[] ) throws Exception { byte data[] = new Date().toString().getBytes(); InetAddress ia = InetAddress.getByName( "localhost" ); int port = 7; DatagramPacket p = new DatagramPacket( data,data.length,ia,port ); System.out.println( "Paket addressiert an " + p.getAddress() + " an Port " + p.getPort() + "\n" + "Mit " + p.getLength() + " Bytes: " + new String(p.getData()) ); } }
Zum Senden eines DatagramPacket dient die DatagramSocket-Methode send(DatagramPacket). Sie schickt das Datagram an die im DatagramPacket enthaltene Port-Nummer und -Adresse. Im oberen Beispiel hatten wir diese Informationen einmal ausgelesen. Die Reihenfolge für Sendevorgänge ist also immer die gleiche: Ein Datagram-Socket mit einem Standard-Konstruktor erzeugen, das DatagramPaket-Objekt mit dem Port und der Inet-Adresse des Empfängers erzeugen, dann schickt send() das Paket auf die Reise. Wir sehen im folgenden Beispiel einen Client, der sich mit einem Server verbindet und einfach die Uhrzeit abschickt. Dies dient der Vorbereitung auf einen eigenen UDP-Zeitserver.
import java.net.*; import java.util.*; class UDPClient { public static void main( String args[] ) { try { DatagramPacket packet; while ( true ) { InetAddress ia = InetAddress.getByName( "localhost" ); String s = new Date().toString(); packet = new DatagramPacket( s.getBytes(),s.length(),ia,4711 ); DatagramSocket dSocket = new DatagramSocket(); dSocket.send( packet ); System.out.println( "Weg is' es" ); Thread.sleep( 1000 ); } } catch ( Exception e ) { System.out.println( e ); } } }
Ein Zeitserver bietet Clients immer die aktuelle Server-Uhrzeit an. Wir halten uns hier an das Daytime Protocol, welches im RFC 867 beschrieben ist. Ein Server lauscht auf Port 13 nach UDP-Paketen1 und sendet dann die Antwort an den Sender, dessen Adresse er aus dem Paket nimmt. Im Gegensatz zu unseren bisher geschriebenen Programmen muss der Client erst ein Paket aufbauen, es senden und dann auf die Antwort warten. Der Server erwartet jedoch kein spezielles Anfrageformat. Er reagiert auf einen beliebigen Inhalt, da er diesen ohnehin verwirft. Die Zeichenkette vom Server muss kein spezielles Format besitzen, doch bietet sich hier eine Zeichenkette mit dem Format »Wochentag, Monat, Tag, Jahr, Zeitzone« an.
Da Client und Server ziemlich ähnlich aufgebaut sind, ist es eigentlich egal, mit welchem Teil wir beginnen. Sie unterscheiden sich wenig, da beide Daten empfangen und senden. Wenn wir einen Client für Zeitdienste programmieren, können wir den eingebauten Daytime-Server nutzen. Unter Unix ist dieser schon vorinstalliert. Unter Windows müssen wir den Dienst erst mit dem Zusatzprogramm Uatime32 aus den Socket-Programmen starten. Da wir aber gleich selber einen Server programmieren werden, lässt sich zum Testen auch ein Kapitel weiter schauen.
Die folgende Implementierung zeigt, wie ein Zeitserver angesprochen wird. Dazu wird zunächst ein leeres Paket als Anfrage an den Server gesendet. Anschließend wird mit receive() auf das hereinkommende Paket gewartet. Wenn dieses kommt, dann liefert getData() eine Zeichenkette mit der Zeit.
Listing 16.21 UDPTimeClient.java
import java.net.*; class UDPTimeClient { public static void main( String args[] ) { try { DatagramPacket packet; while ( true ) { // zunächst die Anfrage an den Server byte data[] = new byte[1024]; InetAddress ia = InetAddress.getByName( "localhost" ); packet = new DatagramPacket( data, data.length, ia, 13 ); DatagramSocket toSocket = new DatagramSocket(); toSocket.send( packet ); // Jetzt vom Server was empfangen DatagramSocket fromSocket = toSocket; //new DatagramSocket(); packet = new DatagramPacket( data, data.length ); fromSocket.receive( packet ); String s = "Server" + //packet.getAddress() + " am Port " + packet.getPort() + " gibt mit die Zeit "+ new String( packet.getData() ); System.out.println( s ); fromSocket.close(); Thread.sleep( 1000 ); } } catch ( Exception e ) { System.out.println( e ); } } }
Der Server liest aus dem Paket die IP-Adresse und Port-Nummer des Senders heraus und schickt ein Paket mit einer Zeiteinheit zurück. Das nachfolgende Programm wartet auf dem Port auf eine Anfrage und gibt die aktuelle Zeit einfach aus.
Listing 16.22 UDPTimeServer.java
import java.net.*; import java.util.*; public class UDPTimeServer { public static void main( String args[] ) { try { byte data[] = new byte[ 1024 ]; DatagramPacket packet; DatagramSocket socket = new DatagramSocket( 13 ); while ( true ) { // Auf Anfrage warten packet = new DatagramPacket( data, data.length ); socket.receive( packet ); // Empfänger auslesen, aber Paketinhalt ignorieren InetAddress adress = packet.getAddress(); int port = packet.getPort(); System.out.println( "Anfrage von " + packet.getAddress() + " am Port " + packet.getPort() ); // Paket für Empfänger zusammenbauen String s = new Date().toString() + "\n"; s.getBytes( 0, s.length(), data, 0 ); packet = new DatagramPacket(data,data.length,adress,port); socket.send( packet ); Thread.sleep( 1000 ); } } catch ( Exception e ) { System.out.println( e ); } } }
1 Den Zeitdienst gibt es auch als TCP-Dienst, der hier aber keine Rolle spielt.
| << zurück |
Copyright © Galileo Press GmbH 2003
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.