From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Zur Adressierung von Rechnern im Netz wird die Klasse InetAddress
+des Pakets java.net
+verwendet. Ein InetAddress-Objekt
+enthält sowohl eine IP-Adresse als auch den symbolischen Namen
+des jeweiligen Rechners. Die beiden Bestandteile können mit den
+Methoden getHostName
+und getHostAddress
+abgefragt werden. Mit Hilfe von getAddress
+kann die IP-Adresse auch direkt als byte-Array
+mit vier Elementen beschafft werden:
+
+
+Um ein InetAddress-Objekt
+zu generieren, stehen die beiden statischen Methoden getByName
+und getLocalHost
+zur Verfügung:
+
+
+getByName
+erwartet einen String mit der IP-Adresse oder dem Namen des Hosts
+als Argument, getLocalHost
+liefert ein InetAddress-Objekt
+für den eigenen Rechner. Beide Methoden lösen eine Ausnahme
+des Typs UnknownHostException
+aus, wenn die Adresse nicht ermittelt werden kann. Das ist insbesondere
+dann der Fall, wenn kein DNS-Server zur Verfügung steht, der
+die gewünschte Namensauflösung erledigen könnte (beispielsweise
+weil die Dial-In-Verbindung zum Provider gerade nicht besteht).
+
+
+Das folgende Listing zeigt ein einfaches Programm, das zu einer IP-Adresse
+den symbolischen Namen des zugehörigen Rechners ermittelt und
+umgekehrt:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 46 - Netzwerkprogrammierung
+
+
+
+
+
+46.2 Client-Sockets
+
+
+
+
+
+
+
+
+46.2.1 Adressierung
+
+
+
+
+
+
+
+
+
+
+String getHostName()
+
+String getHostAddress()
+
+byte[] getAddress()
+
+
+
+java.net.InetAddress
+
+
+
+
+
+
+
+
+
+public static InetAddress getByName(String host)
+ throws UnknownHostException
+
+public static InetAddress getLocalHost()
+ throws UnknownHostException
+
+
+
+java.net.InetAddress
+
+
+
+Listing 46.1: IP-Adressenauflösung
+
+
+
+
+
+001 /* Listing4601.java */
+002
+003 import java.net.*;
+004
+005 public class Listing4601
+006 {
+007 public static void main(String[] args)
+008 {
+009 if (args.length != 1) {
+010 System.err.println("Usage: java Listing4601 <host>");
+011 System.exit(1);
+012 }
+013 try {
+014 //Get requested address
+015 InetAddress addr = InetAddress.getByName(args[0]);
+016 System.out.println(addr.getHostName());
+017 System.out.println(addr.getHostAddress());
+018 } catch (UnknownHostException e) {
+019 System.err.println(e.toString());
+020 System.exit(1);
+021 }
+022 }
+023 }
+
+
+Listing4601.java
+
+Wird das Programm mit localhost
+als Argument aufgerufen, ist seine Ausgabe:
+
+
+localhost
+127.0.0.1
+
+
+
+
![]() |
+
+
+ +localhost ist eine Pseudo-Adresse für +den eigenen Host. Sie ermöglicht das Testen von Netzwerkanwendungen, +auch wenn keine wirkliche Netzwerkverbindung besteht (TCP/IP muss +allerdings korrekt installiert sein). Sollen wirkliche Adressen verarbeitet +werden, muss natürlich eine Verbindung zum Netz (insbesondere +zum DNS-Server) aufgebaut werden können. |
+
+
|
+![]() |
+
+Die nachfolgende Ausgabe zeigt die Ausgabe des Beispielprogramms,
+wenn es nacheinander mit den Argumenten java.sun.com,
+www.gkrueger.com und www.addison-wesley.de
+aufgerufen wird:
+
+
+java.sun.com
+192.18.97.71
+
+www.gkrueger.com
+213.221.123.45
+
+www.addison-wesley.de
+194.163.213.76
+
+
+
+
+
+
+
+Als Socket bezeichnet man eine streambasierte +Programmierschnittstelle zur Kommunikation zweier Rechner in einem +TCP/IP-Netz. Sockets wurden Anfang der achtziger Jahre für die +Programmiersprache C entwickelt und mit Berkeley UNIX 4.1/4.2 allgemein +eingeführt. Das Übertragen von Daten über eine Socket-Verbindung +ähnelt dem Zugriff auf eine Datei: +
+Während die Socket-Programmierung in C eine etwas mühsame +Angelegenheit war, ist es in Java recht einfach geworden. Im wesentlichen +sind dazu die beiden Klassen Socket +und ServerSocket +erforderlich. Sie repräsentieren Sockets aus der Sicht einer +Client- bzw. Server-Anwendung. Nachfolgend wollen wir uns mit den +Client-Sockets beschäftigen, die Klasse ServerSocket +wird im nächsten Abschnitt behandelt. + +
+Die Klasse Socket +besitzt verschiedene Konstruktoren, mit denen ein neuer Socket erzeugt +werden kann. Die wichtigsten von ihnen sind: +
+
+
++public Socket(String host, int port) + throws UnknownHostException, IOException + +public Socket(InetAddress address, int port) + throws IOException ++ + |
++java.net.Socket | +
+Beide Konstruktoren erwarten als erstes Argument die Übergabe +des Hostnamens, zu dem eine Verbindung aufgebaut werden soll. Dieser +kann entweder als Domainname in Form eines Strings oder als Objekt +des Typs InetAddress +übergeben werden. Soll eine Adresse mehrfach verwendet werden, +ist es besser, die zweite Variante zu verwenden. In diesem Fall kann +das übergebene InetAddress-Objekt +wiederverwendet werden, und die Adressauflösung muss nur einmal +erfolgen. Wenn der Socket nicht geöffnet werden konnte, gibt +es eine Ausnahme des Typs IOException +bzw. UnknownHostException +(wenn das angegebene Zielsystem nicht angesprochen werden konnte). + +
+Der zweite Parameter des Konstruktors ist die Portnummer. Wie in Abschnitt 46.1.4 +erwähnt, dient sie dazu, den Typ des Servers zu bestimmen, mit +dem eine Verbindung aufgebaut werden soll. Die wichtigsten Standard-Portnummern +sind in Tabelle 46.2 aufgelistet. + +
+Nachdem die Socket-Verbindung erfolgreich aufgebaut wurde, kann mit +den beiden Methoden getInputStream +und getOutputStream +je ein Stream zum Empfangen und Versenden von Daten beschafft werden: +
+
+
++public InputStream getInputStream() + throws IOException + +public OutputStream getOutputStream() + throws IOException ++ + |
++java.net.Socket | +
+Diese Streams können entweder direkt verwendet oder mit Hilfe +der Filterstreams in einen bequemer zu verwendenden Streamtyp geschachtelt +werden. Nach Ende der Kommunikation sollten sowohl die Eingabe- und +Ausgabestreams als auch der Socket selbst mit close +geschlossen werden. + +
+Als erstes Beispiel wollen wir uns ein Programm ansehen, das eine +Verbindung zum DayTime-Service auf +Port 13 herstellt. Dieser Service läuft auf fast allen UNIX-Maschinen +und kann gut zu Testzwecken verwendet werden. Nachdem der Client die +Verbindung aufgebaut hat, sendet der DayTime-Server einen String mit +dem aktuellen Datum und der aktuellen Uhrzeit und beendet dann die +Verbindung. + + +
+
+
+
+001 /* Listing4602.java */
+002
+003 import java.net.*;
+004 import java.io.*;
+005
+006 public class Listing4602
+007 {
+008 public static void main(String[] args)
+009 {
+010 if (args.length != 1) {
+011 System.err.println("Usage: java Listing4602 <host>");
+012 System.exit(1);
+013 }
+014 try {
+015 Socket sock = new Socket(args[0], 13);
+016 InputStream in = sock.getInputStream();
+017 int len;
+018 byte[] b = new byte[100];
+019 while ((len = in.read(b)) != -1) {
+020 System.out.write(b, 0, len);
+021 }
+022 in.close();
+023 sock.close();
+024 } catch (IOException e) {
+025 System.err.println(e.toString());
+026 System.exit(1);
+027 }
+028 }
+029 }
+
+ |
++Listing4602.java | +
+Das Programm erwartet einen Hostnamen als Argument und gibt diesen
+an den Konstruktor von Socket
+weiter, der eine Verbindung zu diesem Host auf Port 13 erzeugt. Nachdem
+der Socket steht, wird der InputStream
+beschafft. Das Programm gibt dann so lange die vom Server gesendeten
+Daten aus, bis durch den Rückgabewert -1 angezeigt wird, dass
+keine weiteren Daten gesendet werden. Nun werden der Eingabestream
+und der Socket geschlossen und das Programm beendet. Die Ausgabe des
+Programms ist beispielsweise:
+
+
+Sat Nov 7 22:58:37 1998
+
+
+
+
![]() |
+![]() |
+
+
+
+Um in Listing 46.2 den Socket
+alternativ mit einem InetAddress-Objekt
+zu öffnen, wäre Zeile 015
+durch den folgenden Code zu ersetzen:
+
+ |
+
+
|
+![]() |
+
+Nachdem wir jetzt wissen, wie man lesend auf einen Socket zugreift, +wollen wir in diesem Abschnitt auch den schreibenden Zugriff vorstellen. +Dazu schreiben wir ein Programm, das eine Verbindung zum ECHO-Service +auf Port 7 herstellt. Das Programm liest so lange die Eingaben des +Anwenders und sendet sie an den Server, bis das Kommando QUIT +eingegeben wird. Der Server liest die Daten zeilenweise und sendet +sie unverändert an unser Programm zurück, von dem sie auf +dem Bildschirm ausgegeben werden. Um Lese- und Schreibzugriffe zu +entkoppeln, verwendet das Programm einen separaten Thread, der die +eingehenden Daten liest und auf dem Bildschirm ausgibt. Dieser läuft +unabhängig vom Vordergrund-Thread, in dem die Benutzereingaben +abgefragt und an den Server gesendet werden. + + +
+
+
+
+001 /* EchoClient.java */
+002
+003 import java.net.*;
+004 import java.io.*;
+005
+006 public class EchoClient
+007 {
+008 public static void main(String[] args)
+009 {
+010 if (args.length != 1) {
+011 System.err.println("Usage: java EchoClient <host>");
+012 System.exit(1);
+013 }
+014 try {
+015 Socket sock = new Socket(args[0], 7);
+016 InputStream in = sock.getInputStream();
+017 OutputStream out = sock.getOutputStream();
+018 //Timeout setzen
+019 sock.setSoTimeout(300);
+020 //Ausgabethread erzeugen
+021 OutputThread th = new OutputThread(in);
+022 th.start();
+023 //Schleife für Benutzereingaben
+024 BufferedReader conin = new BufferedReader(
+025 new InputStreamReader(System.in));
+026 String line = "";
+027 while (true) {
+028 //Eingabezeile lesen
+029 line = conin.readLine();
+030 if (line.equalsIgnoreCase("QUIT")) {
+031 break;
+032 }
+033 //Eingabezeile an ECHO-Server schicken
+034 out.write(line.getBytes());
+035 out.write('\r');
+036 out.write('\n');
+037 //Ausgabe abwarten
+038 th.yield();
+039 }
+040 //Programm beenden
+041 System.out.println("terminating output thread...");
+042 th.requestStop();
+043 th.yield();
+044 try {
+045 Thread.sleep(1000);
+046 } catch (InterruptedException e) {
+047 }
+048 in.close();
+049 out.close();
+050 sock.close();
+051 } catch (IOException e) {
+052 System.err.println(e.toString());
+053 System.exit(1);
+054 }
+055 }
+056 }
+057
+058 class OutputThread
+059 extends Thread
+060 {
+061 InputStream in;
+062 boolean stoprequested;
+063
+064 public OutputThread(InputStream in)
+065 {
+066 super();
+067 this.in = in;
+068 stoprequested = false;
+069 }
+070
+071 public synchronized void requestStop()
+072 {
+073 stoprequested = true;
+074 }
+075
+076 public void run()
+077 {
+078 int len;
+079 byte[] b = new byte[100];
+080 try {
+081 while (!stoprequested) {
+082 try {
+083 if ((len = in.read(b)) == -1) {
+084 break;
+085 }
+086 System.out.write(b, 0, len);
+087 } catch (InterruptedIOException e) {
+088 //nochmal versuchen
+089 }
+090 }
+091 } catch (IOException e) {
+092 System.err.println("OutputThread: " + e.toString());
+093 }
+094 }
+095 }
+
+ |
++EchoClient.java | +
+Eine Beispielsession mit dem Programm könnte etwa so aussehen
+(Benutzereingaben sind fettgedruckt):
+
+
+guido_k@pc1:/home/guido_k/nettest > java EchoClient localhost
+hello
+hello
+world
+world
+12345
+12345
+quit
+closing output thread...
+
+
+
+
+Wie im vorigen Beispiel wird zunächst ein Socket zu dem als Argument +angegebenen Host geöffnet. Das Programm beschafft dann Ein- und +Ausgabestreams zum Senden und Empfangen von Daten. Der Aufruf von +setSoTimeout +gibt die maximale Wartezeit bei einem lesenden Zugriff auf den Socket +an (300 ms.). Wenn bei einem read +auf den InputStream +nach Ablauf dieser Zeit noch keine Daten empfangen wurden, terminiert +die Methode mit einer InterruptedIOException; +wir kommen darauf gleich zurück. Nun erzeugt das Programm den +Lesethread und übergibt ihm den Eingabestream. In der nun folgenden +Schleife (Zeile 027) +werden so lange Eingabezeilen gelesen und an den Server gesendet, +bis der Anwender das Programm mit QUIT +beendet. +
+
![]() |
+![]() |
+
+
+ +Das Programm wurde auf einer LINUX-Version entwickelt, die noch kein +präemptives Multithreading unterstützt. Die verschiedenen +Aufrufe von yield +dienen dazu, die Kontrolle an den Lesethread zu übergeben. Ohne +diesen Aufruf würde der Lesethread gar nicht zum Zuge kommen +und das Programm würde keine Daten vom Socket lesen. Auf Systemen, +die präemptives Multithreading unterstützen, sind diese +Aufrufe nicht notwendig. |
+
+
|
+![]() |
+
+Die Klasse OutputThread implementiert +den Thread zum Lesen und Ausgeben der Daten. Da die Methode stop +der Klasse Thread +im JDK 1.2 als deprecated +markiert wurde, müssen wir mit Hilfe der Variable stoprequested +etwas mehr Aufwand treiben, um den Thread beenden zu können. +stoprequested steht normalerweise +auf false +und wird beim Beenden des Programms durch Aufruf von requestStop +auf true +gesetzt. In der Hauptschleife des Threads wird diese Variable periodisch +abgefragt, um die Schleife bei Bedarf abbrechen zu können (Zeile 081). + +
+Problematisch bei dieser Technik ist lediglich, dass der Aufruf von +read +normalerweise so lange blockiert, bis weitere Zeichen verfügbar +sind. Steht das Programm also in Zeile 083, +so hat ein Aufruf requestStop +zunächst keine Wirkung. Da das Hauptprogramm in Zeile 048 +die Streams und den Socket schließt, würde es zu einer +SocketException +kommen. Unser Programm verhindert das durch den Aufruf von setSoTimeout +in Zeile 019. Dadurch +wird ein Aufruf von read +nach spätestens 300 ms. mit einer InterruptedIOException +beendet. Diese Ausnahme wird in Zeile 087 +abgefangen, um anschließend vor dem nächsten Schleifendurchlauf +die Variable stoprequested erneut +abzufragen. + + + + +
+Die Kommunikation mit einem Web-Server erfolgt über das HTTP-Protokoll, +wie es in den RFCs 1945 und 2068 beschrieben wurde. Ein Web-Server +läuft normalerweise auf TCP-Port 80 (manchmal läuft er zusätzlich +auch auf dem UDP-Port 80) und kann wie jeder andere Server über +einen Client-Socket angesprochen werden. Wir wollen an dieser Stelle +nicht auf Details eingehen, sondern nur die einfachste und wichtigste +Anwendung eines Web-Servers zeigen, nämlich das Übertragen +einer Seite. Ein Web-Server ist in seinen Grundfunktionen ein recht +einfaches Programm, dessen Hauptaufgabe darin besteht, angeforderte +Seiten an seine Clients zu versenden. Kompliziert wird er vor allem +durch die Vielzahl der mittlerweile eingebauten Zusatzfunktionen, +wie beispielsweise Logging, Server-Scripting, Server-Side-Includes, +Security- und Tuning-Features usw. + +
+Fordert ein Anwender in seinem Web-Browser eine Seite an, so wird
+diese Anfrage vom Browser als GET-Transaktion
+an den Server geschickt. Um beispielsweise die Seite http://www.javabuch.de/index.html
+zu laden, wird folgendes Kommando an den Server www.javabuch.de
+gesendet:
+
+
+GET /index.html
+
+
+
+
+Der erste Teil gibt den Kommandonamen an, dann folgt die gewünschte +Datei. Die Zeile muss mit einer CRLF-Sequenz abgeschlossen werden, +ein einfaches '\n' reicht nicht aus. Der Server versucht nun die angegebene +Datei zu laden und überträgt sie an den Client. Ist der +Client ein Web-Browser, wird er den darin befindlichen HTML-Code interpretieren +und auf dem Bildschirm anzeigen. Befinden sich in der Seite Verweise +auf Images, Applets oder Frames, so fordert der Browser die fehlenden +Seiten in weiteren GET-Transaktionen von deren Servern ab. + +
+Die Struktur des GET-Kommandos wurde mit der Einführung von HTTP
+1.0 etwas erweitert. Zusätzlich werden nun am Ende der Zeile
+eine Versionskennung und wahlweise in den darauffolgenden Zeilen weitere
+Headerzeilen mit Zusatzinformationen mitgeschickt. Nachdem die letzte
+Headerzeile gesendet wurde, folgt eine leere Zeile (also ein alleinstehendes
+CRLF), um das Kommandoende anzuzeigen. HTTP 1.0 ist weit verbreitet,
+und das obige Kommando würde von den meisten Browsern in folgender
+Form gesendet werden (jede der beiden Zeilen muss mit CRLF abgeschlossen
+werden):
+
+
+GET /index.html HTTP/1.0
+
+
+
+
+
+Wird HTTP/1.0 verwendet, ist auch die Antwort des Servers etwas komplexer. +Anstatt lediglich den Inhalt der Datei zu senden, liefert der Server +seinerseits einige Headerzeilen mit Zusatzinformationen, wie beispielsweise +den Server-Typ, das Datum der letzten Änderung oder den MIME-Typ +der Datei. Auch hier ist jede Headerzeile mit einem CRLF abgeschlossen, +und nach der letzten Headerzeile folgt eine Leerzeile. Erst dann beginnt +der eigentliche Dateiinhalt. + +
+Das folgende Programm kann dazu verwendet werden, eine Datei von einem +Web-Server zu laden. Es wird mit einem Host- und einem Dateinamen +als Argument aufgerufen und lädt die Seite vom angegebenen Server. +Das Ergebnis wird (mit allen Headerzeilen) auf dem Bildschirm angezeigt. + + +
+
+
+
+001 /* Listing4604.java */
+002
+003 import java.net.*;
+004 import java.io.*;
+005
+006 public class Listing4604
+007 {
+008 public static void main(String[] args)
+009 {
+010 if (args.length != 2) {
+011 System.err.println(
+012 "Usage: java Listing4604 <host> <file>"
+013 );
+014 System.exit(1);
+015 }
+016 try {
+017 Socket sock = new Socket(args[0], 80);
+018 OutputStream out = sock.getOutputStream();
+019 InputStream in = sock.getInputStream();
+020 //GET-Kommando senden
+021 String s = "GET " + args[1] + " HTTP/1.0" + "\r\n\r\n";
+022 out.write(s.getBytes());
+023 //Ausgabe lesen und anzeigen
+024 int len;
+025 byte[] b = new byte[100];
+026 while ((len = in.read(b)) != -1) {
+027 System.out.write(b, 0, len);
+028 }
+029 //Programm beenden
+030 in.close();
+031 out.close();
+032 sock.close();
+033 } catch (IOException e) {
+034 System.err.println(e.toString());
+035 System.exit(1);
+036 }
+037 }
+038 }
+
+ |
++Listing4604.java | +
+Wird das Programm beispielsweise auf einem SUSE-Linux 5.2 mit frisch
+installiertem Apache-Server mit localhost
+und /index.html als Argument
+aufgerufen, so beginnt seine Ausgabe wie folgt:
+
+
+HTTP/1.1 200 OK
+Date: Sun, 08 Nov 1998 18:26:13 GMT
+Server: Apache/1.2.5 S.u.S.E./5.1
+Last-Modified: Sun, 24 May 1998 00:46:46 GMT
+ETag: "e852-45c-35676df6"
+Content-Length: 1116
+Accept-Ranges: bytes
+Connection: close
+Content-Type: text/html
+
+<HTML>
+<HEAD>
+<TITLE>Apache HTTP Server - Beispielseite</TITLE>
+</HEAD>
+<BODY bgcolor=#ffffff>
+<H1> Der Apache WWW Server </H1> <BR>
+Diese Seite soll nur als Beispiel dienen.
+Die <A HREF="./manual/">Dokumentation zum
+Apache-Server</A> finden Sie hier.
+<P>
+...
+
+
+
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |