1. Mit dem Socket zum Server Die URL-Verbindungen sind schon High-level-Verbindungen und wir müssen uns nicht erst um Übertragungsprotokolle wie HTTP oder noch tiefer TCP/IP kümmern. Aber alle höheren Verbindungen bauen auf Sockets auf, und auch die Verbindung zu einem Rechner über URL ist mit Sockets realisiert. Beschäftigen wir uns also nun etwas mit dem Hintergrund. 1.1 Das Netzwerk ist der Computer Die Rechner, die im Internet verbunden sind, kommunizieren über Protokolle, wobei TCP/IP das wichtigste geworden ist. Die Entwicklung des Protokolls geht in die Achtzigerjahre zurück. Die ARPA (Advanced Research Projects Agency) gab der Universität von Berkeley (Kalifornien) den Auftrag, unter Unix das TCP/IP-Protokoll zu implementieren, um dort in dem Netzwerk zu kommunizieren. 1 Was sich die Kalifornier ausgedacht hatten, fand auch in der Berkeley Software Distribution (BSD), einer Unix-Variante, Verwendung: die Berkeley- Sockets. Mittlerweile hat sich das Berkeley-Socket-Interface über alle Betriebssystemgrenzen hinweg entwickelt und ist der De-facto-Standard für TCP/IP-Kommunikation. Es finden sich in allen möglichen Unix-Derivaten und auch unter Windows Socket-Implementierungen. So ist Windows Socket ein Interface für Microsoft Windows, mit dem sich die Sockets auch unter dieser Plattform nutzen lassen. Die Spezifikation von Windows Socket basiert auf BSD Unix Version 4.3. Ein Socket dient zur Abstraktion und ist ein Verbindungspunkt in einem TCP/IP-Netzwerk. Werden mehrere Computer verbunden, so implementiert jeder Rechner einen Socket: Derjenige, der Daten empfängt (Client), öffnet eine Socket-Verbindung zum Horchen, und derjenige, der sendet, öffnet eine Verbindung zum Senden (Server). Es lässt sich in der Realität nicht immer ganz trennen, wer Client und wer Server ist, da Server ebenfalls zum Datenaustausch Verbindungen aufbauen können. Doch für den Betrachter von außen ist der Server der Wartende und der Client derjenige, der die Verbindung initiiert. Serveradresse und Port Damit der Empfänger den Sender auch hören kann, muss dieser durch eine eindeutige Adresse als Server ausgemacht werden. Er bekommt also eine IP-Adresse im Netz und eine ebenso eindeutige Port-Adresse. Der Port ist so etwas wie eine Zimmernummer im Hotel. Die Adresse bleibt dieselbe, aber in jedem Zimmer sitzt einer und macht seine Aufgaben. Jeder Dienst (Service), den ein Server zur Verfügung stellt, läuft auf einem anderen Port. Eine Port-Nummer ist eine 16-Bit-Zahl und in die Gruppen»System«und»Benutzer«eingeteilt. Die so genannten Well-Known-System-Ports (auch Contact Ports genannt) liegen im Bereich von 0 1023. (Noch vor einigen Jahren haben 255 definierte Nummern ausgereicht.) Die User- Ports umfassen den restlichen Bereich von 1024 65535. Server vergeben die Ports aber nicht willkürlich. Die IANA 2 (Internet Assigned Numbers Authority) ist der Koordinator für IP- Adressen, Domain-Namen, MIME-Typen und für viele andere Parameter, unter anderem auch für die Port-Nummern näheres unter http://www.iana.org/. Die folgende Tabelle zeigt einige wenige dieser Port-Nummern. Die vollständige Liste ist unter http://www.iana.org/assignments/port-numbers verfügbar.
Service Port Beschreibung echo 7 Echo daytime 13 Daytime Tabelle 16.2 Einige ausgewählte System-Ports qotd 17 Quote of the Day ftp-data 20 File Transfer [Default Data] ftp 21 File Transfer [Control] ssh 22 SSH Remote Login Protocol telnet 23 Telnet smtp 25 Simple Mail Transfer time 37 Time nicname 43 Who Is domain 53 Domain Name Server whois++ 63 whois++ gopher 70 Gopher finger 79 Finger www 80 World Wide Web HTTP pop2 109 Post Office Protocol Version 2 pop3 110 Post Office Protocol Version 3 Die auf einem Unix-System installierten Ports sind meistens unter /etc/services einzusehen. Stream-Sockets/Datagram-Sockets Ein Stream-Socket baut eine feste Verbindung zu einem Rechner auf. Das Besondere dabei ist: Die Verbindung bleibt für die Dauer der Übertragung bestehen. Dies ist bei der anderen Form der Sockets, den Datagram-Sockets, nicht der Fall. Wir behandeln die Stream-Sockets zuerst. 1.2 Eine Verbindung zum Server aufbauen Um Daten von einer Stelle zur anderen zu schicken, muss zunächst eine Verbindung zum Server bestehen. Dieser wiederum beantwortet die eingehenden Fragen. Mit den Netzwerkklassen unter Java lassen sich sowohl Client- als auch Server-basierte Programme schreiben. Da die Client-Seite noch einfacher als die Server-Seite ist in Java ist Netzwerkprogrammierung ein Genuss beginnen wir mit dem Client. Dieser muss zu einem horchenden Server verbunden werden. Diese Verbindung wird durch die java.net.socket- Klasse aufgebaut: Socket clientsocket = new Socket( "die.weite.welt", 80 ); Der erste Parameter des Konstruktors erwartet den Namen des Servers (Host-Adresse), mit dem wir uns verbinden wollen. Der zweite Parameter steht für den Port wir haben hier 80 gewählt, um zu einem Web-Server Verbindung aufzubauen.
Hinweis Verbinden wir ein Applet mit dem Server, von dem es geladen wurde, würden wir mit getcodebase().gethost() arbeiten, etwa so: Socket server = new Socket( getcodebase().gethost(), 7 ); Es gibt noch eine andere Möglichkeit, zu einem Host zu gelangen: über die Klasse InetAddress. secondsocket = new Socket( server.getinetaddress(), 1234 ); Alternativ ermittelt die Funktion gethostbyname(string) die InetAddress eines Hosts. Ist der Server nicht erreichbar, so wirft das System bei allen Socket-Konstruktionsversuchen eine UnknownHostException aus; es ist eine Unterklasse von IOException, so dass grundsätzlich ein Auffangen/Weiterleiten einer IOException ausreicht. class java.net. Socket Socket( String host, int port ) throws IOException Erzeugt einen Stream-Socket und verbindet ihn mit der Port-Nummer am angegebenen Host. Socket( InetAddress address, int port ) throws IOException Erzeugt einen Stream-Socket und verbindet ihn mit der Port-Nummer am Host mit der angegebenen IP-Nummer. Socket( String host, int port, InetAddress localaddr, int localport ) throws IOException Erzeugt einen Socket für den Host host am Port port und bindet ihn an die lokale Adresse localaddr und an den lokalen Port localport. Socket( InetAddress address, int port, InetAddress localaddr, int localport ) throws IOException Erzeugt einen Socket für den durch address gegebenen Host am Port port und bindet ihn an die lokale Adresse localaddr und an den lokalen Port localport. Socket() throws IOException Erzeugt einen nicht verbundenen Socket über die Standard-SocketImpl. protected Socket( SocketImpl impl ) throws IOException Erzeugt einen unverbundenen Socket mit einer benutzerdefinierten SocketImpl. Nützlich für Unterklassen mit angepassten Verbindungen, etwa gesicherten Verbindungen. 1.3 Server unter Spannung: die Ströme Besteht erst einmal die Verbindung, so wird mit den Daten vom Server genauso verfahren wie mit den Daten aus einer Datei. Die Socket-Klasse liefert uns Streams, mit denen wir lesen und schreiben können. Nun bietet die Klasse Socket die Methoden getinputstream() und getoutputstream(), die einen Zugang zum Datenstrom erlauben. Holen wir uns zunächst einen Ausgabestrom vom Typ OutputSteam: OutputStream out = server.getoutputstream()
Oft wird dieser dann noch schnell zu einem DataOutputStream oder PrintStream beziehungsweise PrintWrtier gemacht, damit die Ausgabemöglichkeiten vielfältiger sind. Genauso wird mit dem Eingabestrom verfahren. Wandeln wir ihn gleich in einen BufferedReader um: BufferedReader in = new BufferedReader( new InputStreamReader( server.getinputstream()) ); Wir kennen das Prinzip schon von den URL-Verbindungen und von der Dateieingabe/- ausgabe. class java.net. Socket InputStream getinputstream() throws IOException Liefert den Eingabestrom für den Socket. OutputStream getoutputstream() throws IOException Liefert den Ausgabestrom für den Socket. 1.4 Die Verbindung wieder abbauen Die Methode close() leitet das Ende einer Verbindung ein und gibt dem Betriebssystem die reservierten Handles zurück. Ohne Freigabe könnte das Betriebssystem unter Umständen nach einer gewisssen Zeit keine Handles mehr zurückgeben, und weiteres Arbeiten wäre nicht möglich. Dies geht so weit, dass auch der Browser keine HTML-Seite mehr vom Server bekommt. Taucht also das Verhalten auf, dass einige Verbindungen aufgebaut werden können, danach aber Schluss ist, sollte diese Lücke untersucht werden. class java.net. Socket void close() throws IOException Schließt den Socket. 1.5 Ein kleines Echo lebt der Rechner noch? Möchten wir überprüfen, ob ein Rechner in der Lage ist, Kommandos über seine Netzwerkschnittstelle entgegenzunehmen, so können wir ein Kommando hinschicken und warten, ob etwas passiert. Am einfachsten ist der Aufbau zu dem Echo-Server, ein Service, der alle ankommenden Kommandos gleich wieder zurückschickt. Wenn wir den Echo-Dienst nutzen wollen, dann senden wir ein Testwort zum Server und überprüfen, ob das gleiche Wort wieder zurückkommt. Die Herstellung der Verbindung zum Echo-Server ist mit der Socket-Klasse kein Problem. Da der Echo-Service immer an Port 7 liegt, eröffnet die Anweisung Socket(IPAdress, 7) die Verbindung. Anschließend lassen sich InputStream und OutputStream holen, und die Anfrage lässt sich verarbeiten. Die IP-Adresse lesen wir aus der Kommandozeile oder nutzen localhost. Läuft der Echo-Dienst unter einem
Rechner nicht, dann wird eine»java.net.connectexception: Connection refused: connect«- Ausnahme angezeigt. Listing 16.11 Echo.java import java.io.*; import java.net.*; class Echo { public static void main( String args[] ) { Socket t = null; try { t = new Socket( args.length == 0? "localhost" : args[0], 7 ); PrintStream os = new PrintStream( t.getoutputstream() ); String test = "Superkalifragilistischexpialigetisch"; os.println( test ); BufferedReader in = new BufferedReader( new InputStreamReader( t.getinputstream()) ); String s = in.readline(); if ( s.equals(test))system.out.println( "Hurra, er lebt!" ) ; catch ( /* UnknownHostException is a */ IOException e ) { e.printstacktrace(); finally { if ( t!= null ) try { t.close(); catch ( IOException e ) { 1.6 Blockierendes Lesen Eine Eigenschaft ist bei der Server-Programmierung zu beachten: Erwartet der Client aus dem InputStream Daten, der Server schickt aber keine, blockiert die Funktion. Aus diesem Problem gibt es zwei Auswege: Ein völlig anderer Ansatz mit NIO und das einfache Schließen des Sockets mit close(). Denn wenn der Socket geschlossen wird, werden alle Datenstrom-Operationen abgebrochen und eine IOException wird ausgelöst. Damit ist ein gutes Mittel gefunden, um wenigstens blockierte Socket-Verbindungen wieder los zu bekommen. Das soll auch das nächste Beispiel zeigen. Eine Echo-Verbindung schickt ein Zeichen und holt es wieder ein. Dann erfolgt nur ein Lesen. Da aber vom Server kein Zeichen gesendet wird, hängt read() und wartet auf ein Byte. Alles das geschieht in einem Thread. Nach dem Start wird eine Sekunde später der Socket geschlossen, was zum Abbruch von read() und in den Anweisungsblock der Exception-Behandlung führt.
Listing 16.12 CloseConnection.java import java.io.ioexception; import java.net.*; public class CloseConnection { static Socket t; public static void main( String args[] ) throws Exception { t = new Socket( args.length == 0? "localhost" : args[0], 7 ); new Thread( new Runnable() { public void run() { try { t.getoutputstream().write( a ); System.out.println( (char) t.getinputstream().read() ); System.out.println( t.getinputstream().read() ); // Hier hängt er! catch ( IOException e ) { System.err.println( "Blockierung gelöst" ); ).start(); Thread.sleep( 1000 ); t.close(); // Löst die Blockierung Mit der Ausgabe a Blockierung gelöst 1.7 Informationen über den Socket Wie beim URL-Objekt, so lässt auch die Klasse Socket keine grundsätzlich wichtigen Änderungen zu. Port-Adresse wie auch das Ziel müssen beim Erzeugen bekannt sein. Aber, wie bei einer URL auch, es lassen sich Informationen über das Socket-Objekt einholen. class java.net. Socket InetAddress getinetaddress() Liefert die Adresse, mit der der Socket verbunden ist. InetAddress getlocaladdress() Liefert die lokale Adresse, an die der Socket gebunden ist. int getport() Gibt den Remote-Port zurück, mit dem der Socket verbunden ist. int getlocalport() Gibt den lokalen Port des Sockets zurück.
Weitere Funktionen kommen noch hinzu, die allerdings an einem Beispiel demonstriert werden sollen: Listing 16.13 SocketProperties.java import java.io.printstream; import java.net.socket; public class SocketProperties { public static void main( String args[] ) throws Exception { Socket s = new Socket( "java-tutor.com", 80 ); PrintStream p = System.out; p.println( s.getlocaladdress() ); // /192.168.123.137 p.println( s.getlocalport() ); // 1456 p.println( s.getlocalsocketaddress() ); // /192.168.123.137:1456 p.println( s.getremotesocketaddress() ); // javatutor.com/194.9.168.22:80 p.println( s.getport() ); // 80 p.println( s.getreceivebuffersize() ); // 8192 p.println( s.getsendbuffersize() ); // 8192 p.println( s.getsolinger() ); // 1 p.println( s.gettrafficclass() ); // 0 p.println( s.getkeepalive() ); // false p.println( s.getoobinline() ); // false p.println( s.getreuseaddress() ); // false p.println( s.gettcpnodelay() ); // false 2. Datagram-Sockets Neben den Stream-Sockets gibt es im java.net-paket eine weitere Klasse, die auch den verbindungslosen Pakettransport erlaubt. Es handelt sich dabei um die Klasse DatagramSocket. Datagram-Sockets basieren auf dem User Datagram Protocol (UDP). Dies ist auf dem Internet-Protokoll aufgesetzt und erlaubt die ungesicherte Übertragung es ist auf der Transportschicht des OSI-Modells (Schicht 4) angeordnet. Auch UDP erlaubt es einer Applikation, einen Service über einen Port zu kontaktieren. Genau wie TCP nutzt auch UDP verschiedene Port-Nummern, so dass mehrere Server unter unterschiedlichen Ports ihre Dienste anbieten können. Wichtig ist, dass UDP-Ports völlig eigenständig sind und mit TCP- Ports nichts gemeinsam haben. So kann ein Server-Socket für TCP am Port 4711 horchen und ein Datagram-Socket auch. Jedoch lässt sich für ein Programm nicht unbedingt jeder Port nutzen, da etwa das Unix-Betriebssystem einige Ports reserviert beziehungsweise wir nicht unter die 1024-Grenze kommen. Wir werden später ein Programm kennen lernen, welches freie Ports überprüft. Die Datagram-Sockets benötigen im Gegensatz zu den Stream-Sockets keine feste Verbindung zum Server; jedes Datagramm wird einzeln verschickt und kann folglich auf verschiedenen Wegen und in verschiedener Reihenfolge am Client ankommen. So ist der Ausdruck»verbindungslos«zu verstehen. Die Datagramme sind von den anderen völlig unabhängig. Ist die Ordnung der Pakete relevant, muss über ein Zeitfeld dann die richtige Reihenfolge rekonstruiert werden.
Datagram-Sockets und Stream-Sockets im Vergleich Stream-Sockets nutzen eine TCP/IP-Verbindung und die Fähigkeit, Daten in der richtigen Reihenfolge zu sortieren. Arbeiten wir also mit Stream-Sockets oder auch mit der URL- Klasse, so müssen wir uns um den Transport nicht kümmern. Wir werden also bei der Benutzung von Stream-Sockets von den unteren Netzwerkschichten getrennt, die die richtige Reihenfolge der Pakete garantieren. Datagram-Sockets nutzen ein anderes Protokoll: das UDP-Protokoll. Dabei wird nur ein einzelner Chunk durch die Klasse DatagramPacket repräsentiert übertragen, dessen Größe wir fast frei bestimmen können. Da jedoch UDP wie TCP das IP-Protokoll nutzt, ist die Größe eines Datagramms durch das Internet-Protokoll beschränkt und beträgt maximal 64 KB (65.535 Byte). Davon werden allerdings ein paar Byte für den Header benötigt, für Daten wie Sender- und Empfängeradresse und Port-Nummer. Eine Checksumme wie CRC ist nicht nötig. Ziehen wir die Bytes für den Header ab, beträgt der nutzbare Bereich 65.507 Byte. Mehr Daten können wir mit einer Übertragung nicht senden. Es ist somit unsere Aufgabe, größere Pakete zu zerteilen. TCP würde diese Pakete dann wieder richtig zusammensetzen, doch UDP leistet dies nicht. Deswegen garantiert UDP auch nicht, dass die Reihenfolge der Pakete richtig ist. Da UDP nicht mit verlorenen Paketen umgehen kann, ist es nicht gewährleistet, dass alle Daten übertragen werden. Die Anwendung muss sich also selbst darum kümmern. Das hört sich jetzt alles mehr nach einem Nachteil als nach einem Vorteil an. Warum werden dann überhaupt Datagram-Sockets verwendet? Die Antwort ist einfach: Datagram-Sockets sind schnell. Da die Verbindung nicht verbindungsorientiert ist wie TCP/IP, lässt sich der Aufwand für die korrekte Reihenfolge und noch weitere Leistungen sparen. Verbindungslose Protokolle wie eben UDP bauen keine Verbindung zum Empfänger auf und senden dann die Daten, sondern sie senden einfach die Daten und lassen sie von den Zwischenstationen verteilen. UDP profitiert also davon, dass die Bestätigung der Antwort und die erlaubte Möglichkeit des Sendens nicht vereinbart werden. UDP sendet seine Pakete demnach einfach in den Raum, und es ist egal, ob sie ankommen oder nicht. Da allerdings Pakete verloren gehen können, würden wir Datagram-Sockets nicht für große Daten verwenden. Für kleine, öfters übermittelte Daten eignet sich das Protokoll besser. Nehmen wir einmal an, ein Server sendet Börsendaten für die Interessenten. Dafür ist das UDP-Protokoll gut geeignet, denn die anfragenden Clients können auf ein Datenpaket vermutlich verzichten. Wir können davon ausgehen, dass der Server in regelmäßigen Abständen neue Pakete sendet. Hier geht also Geschwindigkeit vor Sicherheit. Bei einer Audio-Übertragung ist es beispielsweise besser, wenn das Paket verschwindet, als wenn das Paket erst zwei Minuten später ankommt und dann abgespielt wird. Das bedeutet, UDP kann überall dort eingesetzt werden, wo eine Empfangsbestätigung nicht relevant ist. Erhält ein Client innerhalb einer gewissen Zeit keine Antwort, so stellt er seine Anfrage einfach erneut. Wichtige Applikationen, die UDP nutzen, sind das Domain Name System (DNS), TFTP (Trivial File Transfer Protocol) und auch Suns Network Filesystem (NFS). NFS ist so ausgelegt, dass verloren gegangene Pakete wieder besorgt werden. Welche Klasse für welche Übertragung verwenden? Im Gegensatz zu TCP-Verbindungen gibt es bei UDP-Verbindungen kein Objekt wie Socket oder ServerSocket für Client und Server. Das liegt daran, dass es in UDP kein Konzept wie virtuelle Verbindungen gibt und die Adresse nicht im Socket gespeichert ist, sondern im Paket selbst. Die Dateneinheiten sind Datagramme, und nach einer Kommunikation wissen die
Partner schon nichts mehr übereinander. Bei UPD verwenden beide die Klasse DatagramSocket, welche für eine eingehende und auch ausgehende Verbindung steht. Tabelle 16.5 Welche Klasse wofür? Klasse Protokoll Verbindungstyp Richtung Socket ServerSocket TCP TCP DatagramSocket UDP 2.1 Die Klasse DatagramSocket Verbindungsorientiert, korrekte Reihenfolge Verbindungsorientiert, korrekte Reihenfolge Verbindungslos, Datagramme, beliebige Reihenfolge Ausgehend Hereinkommend Ausgehend und hereinkommend Damit wir später einmal ein Paket (durch die Klasse DatagramPacket repräsentiert) senden können, erzeugen wir zunächst ein DatagramSocket-Objekt. Dieses Objekt steht für einen Kommunikationspunkt auf unserer Rechnerseite. Im Konstruktor wird hier noch nicht die IP- Adresse des Empfängers eingegeben. Dies geschieht später durch DatagramPacket, da nur im Paket diese Informationen kodiert sind. class java.net. DatagramSocket DatagramSocket() throws SocketException DatagramSocket( int port ) throws SocketException DatagramSocket( int port, InetAddress laddr ) throws SocketException DatagramSocket( SocketAddress bindaddr ) throws SocketException 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. 2.1 Datagramme und die Klasse DatagramPacket Zum Senden und Empfangen wird in beiden Fällen die Klasse DatagramPacket benutzt. Hier sind zwei Fälle zu unterscheiden, die verschiedene Konstruktoren implementieren.
Ein Paket zum Empfang vorbereiten Wenn wir Daten empfangen, dann müssen wir nur ein DatagramPacket-Objekt anlegen und den Speicherplatz angeben, an dem die Daten abgelegt werden sollen. Das Feld ist so etwas wie ein Platzhalter. Folgende Zeilen reichen für einen Server, der am Port des Duftes 4711 horcht: byte data[] = new Bytes[1024]; DatagramSocket socket = new DatagramSocket( 4711 ); DatagramPacket packet = new DatagramPacket( data, data.length ); socket.receive( packet ); 2.2 Auf ein hereinkommendes Paket warten 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 als Argument übergebenen DatagramPacket 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. Listing 16.19 UDPServer.java import java.net.*; 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 );
2.3 Ein Paket zum Senden vorbereiten 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 ); 2.4 Methoden der Klasse DatagramPacket 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.20 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()) ); 2.5 Das Paket senden 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-Zeit-Server. Listing 16.21 UDPClient.java 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 );