Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 17 Netzwerkprogrammierung
  gp 17.1 Grundlegende Begriffe
    gp 17.1.1 Internet-Standards und RFC
  gp 17.2 URL-Verbindungen
    gp 17.2.1 URL-Objekte erzeugen
    gp 17.2.2 Informationen über eine URL
    gp 17.2.3 Der Zugriff auf die Daten über die Klasse URL
  gp 17.3 Die Klasse URLConnection
    gp 17.3.1 Methoden und Anwendung von URLConnection
    gp 17.3.2 Protokoll- und Content-Handler
    gp 17.3.3 Im Detail: Von URL zu URLConnection
  gp 17.4 Das Common Gateway Interface
    gp 17.4.1 Parameter für ein CGI-Programm
    gp 17.4.2 Codieren der Parameter für CGI-Programme
    gp 17.4.3 Eine Suchmaschine ansprechen
  gp 17.5 Host-Adresse und IP-Adressen
    gp 17.5.1 IPv6 für Java mit Jipsy
  gp 17.6 Socket-Programmierung
    gp 17.6.1 Das Netzwerk ist der Computer
    gp 17.6.2 Standarddienste unter Windows nachinstallieren
    gp 17.6.3 Stream-Sockets
    gp 17.6.4 Informationen über den Socket
    gp 17.6.5 Mit telnet an den Ports horchen
    gp 17.6.6 Ein kleines Ping – lebt der Rechner noch?
  gp 17.7 Client/Server-Kommunikation
    gp 17.7.1 Ein Multiplikations-Server
  gp 17.8 Webprotokolle mit NetComponents nutzen
    gp 17.1.1 Internet-Standards und RFC
    gp 17.2.1 URL-Objekte erzeugen
    gp 17.2.2 Informationen über eine URL
    gp 17.2.3 Der Zugriff auf die Daten über die Klasse URL
    gp 17.3.1 Methoden und Anwendung von URLConnection
    gp 17.3.2 Protokoll- und Content-Handler
    gp 17.3.3 Im Detail: Von URL zu URLConnection
    gp 17.4.1 Parameter für ein CGI-Programm
    gp 17.4.2 Codieren der Parameter für CGI-Programme
    gp 17.4.3 Eine Suchmaschine ansprechen
    gp 17.5.1 IPv6 für Java mit Jipsy
    gp 17.6.1 Das Netzwerk ist der Computer
    gp 17.6.2 Standarddienste unter Windows nachinstallieren
    gp 17.6.3 Stream-Sockets
    gp 17.6.4 Informationen über den Socket
    gp 17.6.5 Mit telnet an den Ports horchen
    gp 17.6.6 Ein kleines Ping – lebt der Rechner noch?
    gp 17.7.1 Ein Multiplikations-Server
    gp 17.9.1 Wie eine E-Mail um die Welt geht
    gp 17.9.2 Übertragungsprotokolle
    gp 17.9.3 Das Simple Mail Transfer Protocol
    gp 17.9.4 Demoprogramm, welches eine E-Mail abschickt
    gp 17.10.1 Das Hypertext Transfer Protocol (HTTP)
    gp 17.10.2 Anfragen an den Server
    gp 17.10.3 Die Antworten vom Server
    gp 17.11.1 Die Klasse DatagramSocket
    gp 17.11.2 Datagramme und die Klasse DatagramPacket
    gp 17.11.3 Auf ein hereinkommendes Paket warten
    gp 17.11.4 Ein Paket zum Senden vorbereiten
    gp 17.11.5 Methoden der Klasse DatagramPacket
    gp 17.11.6 Das Paket senden
    gp 17.11.7 Die Zeitdienste und ein eigener Server und Client
    gp 17.12.1 Ping
  gp 17.9 E-Mail verschicken
    gp 17.9.1 Wie eine E-Mail um die Welt geht
    gp 17.9.2 Übertragungsprotokolle
    gp 17.9.3 Das Simple Mail Transfer Protocol
    gp 17.9.4 Demoprogramm, welches eine E-Mail abschickt
  gp 17.10 Arbeitsweise eines Web-Servers
    gp 17.10.1 Das Hypertext Transfer Protocol (HTTP)
    gp 17.10.2 Anfragen an den Server
    gp 17.10.3 Die Antworten vom Server
  gp 17.11 Datagram-Sockets
    gp 17.11.1 Die Klasse DatagramSocket
    gp 17.11.2 Datagramme und die Klasse DatagramPacket
    gp 17.11.3 Auf ein hereinkommendes Paket warten
    gp 17.11.4 Ein Paket zum Senden vorbereiten
    gp 17.11.5 Methoden der Klasse DatagramPacket
    gp 17.11.6 Das Paket senden
    gp 17.11.7 Die Zeitdienste und ein eigener Server und Client
  gp 17.12 Internet Control Message Protocol (ICMP)
    gp 17.12.1 Ping
  gp 17.13 Multicast-Kommunikation

Kapitel 17 Netzwerkprogrammierung

Sicherheit beruht auf der vermeintlichen Kenntnis und
der tatsächlichen Unkenntnis der Zukunft.
– Helmut Nahr

Verbindungen von Rechnern unter Java aufzubauen, ist ein Kinderspiel – somit ist die Netzwerkprogrammierung, die heutzutage noch aufwändig und kompliziert ist, schnell erledigt. Die API-Funktionen sind in ein eigenes Paket geflossen: java.net. Für Sun Microsystems sind Netzwerke das zentrale Computerthema, und sie haben deshalb den Slogan »The network is the computer« gleich als Warenzeichen eingetragen.


Galileo Computing

17.1 Grundlegende Begriffe  downtop

Genauso wie in anderen Bereichen, gibt es in der Netzwerktechnik eine Reihe von Begriffen, deren Bedeutungen bekannt sein sollten. Wir wollen daher für die wichtigsten eine Definition angeben.

gp  Host: Eine Maschine im Netzwerk, die durch eine eindeutige Adresse (IP-Nummer) angesprochen werden kann.
gp  IP-Nummer: Eine eindeutige Adresse, die jeden Host im Internet kennzeichnet. Die Adresse ist eine 32-Bit Zahl, die in die Teile Host und Netzwerk unterteilt ist.
gp  Hostname: Ein symbolischer Name für die IP-Nummer. Durch Techniken wie DNS (Domain Name Service) und Suns NIS (Network Information Services) werden diese auf die IP-Adressen abgebildet.
gp  Paket (engl. Packet): Eine einzelne, über das Netzwerk verschickte Nachricht.
gp  Router: Ein Host, der Pakete zwischen verschiedenen Netzwerken weiterreicht.
gp  IETF: Die Internet Engineering Task Force. Eine Gruppe, die sich um Standards im Internet kümmert.

Galileo Computing

17.1.1 Internet-Standards und RFC  downtop

Das Kürzel RFC – die Buchstaben stehen für Request For Comment – wird uns im Folgenden noch öfter begegnen. RFCs sind frei verfügbare Artikel, in denen Standardisierungsvorschläge offiziell gemacht werden, die sich dann zum Standard etablieren sollen. Sie sind nicht so förmlich wie Normen (DIN, ISO oder IEEE), aber dennoch sehr weitreichend, und gelten als de facto-Standard. Jedes RFC wird durch eine eigene Nummer referenziert, so ist das Internet Protocol (das IP in TCP/IP) in der RFC 791 und das Protokoll, mit den E-Mails befördert werden, in RFC 821 beschrieben. Der Diskussionsprozess selbst ist in der RFC 1310 beschrieben. Der Titel ist »The Internet Standards Process«. Wer selbst Ideen für einen Standard (Proposed Standard) hat, übergibt diesen der Internet Engineering Task Force (IETF). Die Vorschläge werden dann diskutiert und können dann, falls stabil, sinnvoll und verständlich, zur RFC werden. Falls zwei unterschiedliche Implementierungen existieren, kann dieser Vorschlag dann nach spätestens einem Jahr offiziell werden.

Der Text einer RFC kann über verschiedene Stellen in den Rechner kommen: Über Internetseiten, FTP, E-Mail oder CD-ROM. Die Fachhochschule Köln (http://rfc.fh-koeln.de/rfc.html) hat eine große Sammlung von RFCs im HTML-Format. Daneben besitzt die Universität von Ohio die Dokumente. Ist eine spezielle RFC gewünscht, zum Beispiel RFC 1500, so setzen wir die URL http://www.cis.ohio-state.edu/htbin/rfc/rfc1500.html in unseren Browser ein.


Galileo Computing

17.2 URL-Verbindungen  downtop

Eine Verbindung über eine URL (Uniform Resource Locator) ist am leichtesten zu benutzen und zu programmieren. Eine URL (RFC 1738) ist das Adressenformat für eine Ressource im Web. Sie ist also für das Internet so etwas wie ein Dateiname für das Dateisystem. Die allgemeinste Form einer URL ist Folgende:

Schema:Spezialisierung des Schemas

Eine URL enthält den Namen des benutzten Schemas und anschließend nach einem Doppelpunkt erfolgt eine Spezialisierung, die vom Schema abhängt. Nach dem RFC 1738 werden folgende Schemas unterschieden:

Tabelle 17.1   Schemas nach dem RFC 1738
Schema Bedeutung des Schemas
ftp File Transfer Protocol
http Hypertext Transfer Protocol
gopher Gopher Protocol
mailto Elektronische Mail
news USENET News
nntp USENET Newsmit NNTP Zugang
telnet Interaktive Session
wais Wide Area Information Servers
file Hostspezifischer Dateiname
prospero Prospero Directory Service

Wir wollen die Schemas im Folgenden auch Protokoll nennen.

Das Protokoll bestimmt die Zugriffsart und das am meisten verwendete Protokoll ist mittlerweile HTTP (Hypertext Transfer Protocol) mit dem auf Inhalte des Webs zugegriffen wird. Die URL für die Dienste im Web beginnt mit »http«. Sun unterstützt im JDK folgende Protokolle: »doc«, »file«, »ftp«, »gopher«, »http«, »jar«, »mailto«, »system«, »verbatim«. Unterschiedliche Implementierungen haben mehr oder weniger unterstützte Protokolle.

Während die Syntax für oben nicht genannte Adressierungen schwanken, so lässt sich für die IPbasierten Protokolle (IP für »Internet Protocol«) eine Syntax für den schemenspezifischen Teil ausmachen. Dieser beginnt nach einem Doppel-Slash. Somit ist deutlich, dass diese Angabe dem Internet-Schema folgt:

//user:password@host:port/url-path

Einige oder alle Teile können bei einer URL ausgelassen werden. So sind »user:password@«, »:password«, »:port« und »/url-path« optional. Sind Benutzername und dass Passwort angegeben, so folgt ein At-Zeichen »@«. Natürlich dürfen im Passwort und Benutzernamen Doppelpunkt, At-Zeichen oder Slash nicht vorkommen.

Die einzelnen Komponenten haben folgende Bedeutung:

gp  user
Ein optimaler Benutzername. Dieser kann für den Zugriff über ftp vergeben werden.
gp  password
Ein optionales Passwort. Es folgt getrennt durch einen Doppelpunkt nach dem Benutzernamen. Ein leerer Benutzername und ein leeres Passwort (etwa ftp://@host.com) sind etwas anders als kein Benutzername bzw. Passwort (zum Bsp. ftp://host.com). Ein Passwort ohne Benutzernamen kann nicht angegeben werden. Umgekehrt natürlich schon: so hat ein ftp://oing:@host.com einen Benutzernamen, aber ein leeres Passwort.
gp  host
Auf die Angabe des Protokolles folgt der Name der Domäne oder die IP-Adresse des Servers. Name und IP-Adressen sind in der Regel gleichwertig, da von einem besonderen Dienst der Name in eine IP-Adresse umgesetzt wird – verlangt doch eine achtstellige IP-Adresse ein zu gutes Gedächtnis.
gp  port
Eine Verbindung zu einem Rechner entsteht immer durch eine Art Tür, die Port genannt wird. Diese Port-Nummer lässt den Server die Dienste kategorisieren. Jeder Dienst bekommt eine andere Portnummer, damit sie sich unterscheiden lassen. Normalerweise horcht der HTTP-Server auf Port 80.
gp  url-path
Auf den Servernamen folgt die Angabe der Datei, auf der wir via HTTP oder FTP zugreifen wollen. Da sie in einem Verzeichnis liegen kann, beschreibt die folgende Angabe den Weg zur Datei. Ist keine Datei vorhanden und endet die Angabe der URL mit einem Slash »/«, so versucht der Web-Server, auf eine der Dateien index.html oder index.htm zuzugreifen.

Galileo Computing

17.2.1 URL-Objekte erzeugen  downtop

Um ein URL-Objekt zu erzeugen, ist es am einfachsten, über eine String-Repräsentation der URL-Adresse zu gehen.

Beispiel Um eine Verbindung zum Host der Universität Paderborn zu bekommen, nehmen wir die bekannte URL und erzeugen damit das Objekt:
URL uniURL = new URL( "http://uni-paderborn.de/index.html" 
);

Die URL-Klasse besitzt noch zusätzliche Konstruktoren; diese sind dann nützlich, wenn Komponenten der Adresse, also Zugriffsart (beispielsweise das HTTP), Hostname und Dateireferenz getrennt gegeben sind.

Beispiel Eine Alternative zur oberen Form
URL uniURL = new URL( "http", "uni-paderborn.de", 
"index.html" );

Das zweite Argument ist die Basisadresse der URL. Was dem String im dritten Parameter übergeben wird, ist der Ressource-Namen relativ zur Basisadresse. Ist diese Basisadresse null, was möglich ist, dann ist die zweite Angabe absolut zu nehmen. Und ist der zweite Parameter in absoluter Notation formuliert, wird alles im ersten Parameter ignoriert.

Da eine URL auch einen entfernten Rechner an einem anderen Port ansprechen kann, existiert auch dafür ein Konstruktor:

URL url = new URL( "http", "uni-paderborn.de", 
80, "index.html" );

Die URL des Objekts wurde durch eine absolute Adresse erzeugt. Diese enthält dann alle Informationen, die für den Aufbau zum Host nötig sind. Es können jedoch auch URL-Objekte erzeugt werden, wo nur eine relative Angabe bekannt ist. Relative Angaben werden häufig bei HTML-Seiten verwendet, da somit die Seite besser vor Verschiebungen geschützt ist. Damit die Erzeugung eines URL-Objekts mit relativer Adressierung gelingt, muss eine Basisadresse bekannt sein. Ein Konstruktor für relative Adressen erwartet diese Basisadresse als Parameter.

Beispiel Für die Uni-Seite nutzen wir ein URL-Objekt, welches auf die Datei index.html zeigt:
URL uniURL   = new URL( "http://uni-paderborn.de" 
);
URL uniIndex = new URL( uniURL, "index.html");

Diese Art und Weise der URL-Objekt-Erzeugung ist besonders praktisch für Referenzen innerhalb von Web-Seiten (Named Anchors).

Beispiel Besteht für eine Seite ein Unterteilung in TOP und BOTTOM, so kann der URL-Konstruktor für relative URLs verwendet werden.
URL virtuellURL       = new URL( 
"http://bum.bum.org" );
URL virutellBottomURL = new URL( virtuellURL, "#BOTTOM" );

Jeder der Konstruktoren wirft eine MalformedURLException, wenn der Parameter im Konstruktor entweder null ist oder er ein unbekanntes Protokoll (wie in telepatic:\\ulli\brain\java) beschreibt. Somit ist der Code in der Regel von einem Block der folgenden Art umgeben:

try {
URL myURL = new URL( . . . )
}
catch ( MalformedURLException e ) {
// Fehlerbehandlung
}
class java.net.URL
implements Serializable, Comparable

gp  URL( String protocol, String host, int port, String file )
throws MalformedURLException
Erzeugt ein URL-Objekt mit dem gegebenen Protokoll, Hostnamen, Portnummer und Datei. Ist die Portnummer -1, so wird der Standard-Port verwendet, zum Beispiel für das WWW der Port 80.
gp  URL( String protocol, String host, String file )
throws MalformedURLException
Das Gleiche wie URL(protocol, host, -1, file).
gp  URL( String ) throws MalformedURLException
Erzeugt ein Objekt aus der URL-Zeichenkette.
gp  URL( URL, String ) throws MalformedURLException
Erzeugt relativ zur URL ein neues URL-Objekt.
Abbildung


Galileo Computing

17.2.2 Informationen über eine URL  downtop

Ist das URL-Objekt einmal angelegt, so lassen sich die Attribute des Objekts nicht mehr ändern. Es gibt zwar Setter-Methoden, aber diese sind protected und den Unterklassen vorbehalten. Uns normalen Klassenbenutzern bietet die URL-Klasse nur Methoden zum Zugriff. So lassen sich Protokoll, Hostname, Port Nummer und Dateinamen mit Zugriffsmethoden erfragen. Es lassen sich jedoch nicht alle URL-Adressen so detailliert aufschlüsseln und außerdem sind manche der Zugriffsmethoden nur für HTTP sinnvoll.

gp  String getProtocol()
Liefert das Protokoll der URL.
gp  String getHost()
Liefert den Hostnamen der URL, falls dies möglich ist. Für das Protokoll »file« ist dies ein leerer String.
gp  int getPort()
Lierfert die Portnummer. Ist sie nicht gesetzt, liefert getPort() eine -1.
gp  String getFile()
Gibt den Dateinamen der URL zurück.
gp  String getRef()
Gibt die relative Adresse der URL zurück.
final class java.net.URL
implements Serializable, Comparable

Das kleine nachfolgende Programm erzeugt ein URL-Objekt zu http://java.sun.com. Alle Möglichkeiten zur Angabe von URL-Informationen werden ausgenutzt. Anschließend erfolgt ein Auslesen aller Attribute.

Listing 17.1   ParseURL.java
import java.net.*;
import java.io.*;

class ParseURL
{
public static void main( String args[] )
{
try {
URL aURL = new URL(
"http://java.sun.com:80/tutorial/intro.html#DOWNLOADING");

System.out.println( "protocol = " + aURL.getProtocol() );
System.out.println( "host = " + aURL.getHost() );
System.out.println( "filename = " + aURL.getFile() );
System.out.println( "port = " + aURL.getPort() );
System.out.println( "ref = " + aURL.getRef() );
} catch ( MalformedURLException e ) {
System.out.println( "MalformedURLException: " + e );
}
}
}

Und dies ist die Ausgabe:

protocol = http
host = java.sun.com
filename = /tutorial/intro.html
port = 80
ref = DOWNLOADING

Verweisen die URLs auf die gleiche Seite?

Die Methode equals() aus der Klasse Object ist uns bekannt. Sie soll von jeder Klasse so implementiert werden, dass gleiche Objekte true zurückliefern. Jede Klasse soll aber selbst ihre Inhalte vergleichen und nicht nur ihre Objektreferenzen. So muss also die URL-Klasse untersuchen, ob alle Komponenten der einen URL mit der anderen URL übereinstimmen. equals() untersucht dafür zuerst, ob es sich bei der vergleichenden Klasse um ein Exemplar von URL handelt. Wenn ja, wird untersucht, ob die Komponenten Referenzen besitzen oder nicht. Dies wird erreicht, indem Protokoll, Host, Port und Datei untersucht werden. Hierfür bietet sich auch die öffentliche Methode sameFile()an. Ein Anker ist für den Vergleich nicht bestimmend.

public boolean sameFile(URL other) 
{
// AVH: should we not user getPort to compare ports?
return protocol.equals(other.protocol) &&
hostsEqual(host, other.host) &&
(port == other.port) &&
file.equals(other.file);
}

Die Implementierung verrät uns, dass sich die Entwickler nicht einig sind, ob die Ports nicht besser mit getPort() zu vergleichen sind.

final class java.net.URL
implements Serializable, Comparable

gp  boolean sameFile( URL )
Vergleicht zwei URL-Objekte. Die Methode liefert true, falls beide Objekte auf die gleiche Ressource zeigen. Der Anker der HTML-Dateien ist unwichtig.
Beispiel equals() für URL-Objekte Listing 17.2   URLContentsTheSame.java
import java.net.*;

class URLContentsTheSame
{
public static void main( String args[] )
{
try
{
URL sunsite = new URL(
"http://sunsite.unc.edu/javafaq/oldnews.html");
URL helios = new URL(
"http://helios.oit.unc.edu/javafaq/oldnews.html");

if ( sunsite.equals(helios) )
System.out.println( sunsite + " = " + helios );
else
System.out.println( sunsite + " != " + helios );
}
catch ( MalformedURLException e ) {
System.err.println(e);
}
}
}


Galileo Computing

17.2.3 Der Zugriff auf die Daten über die Klasse URL  downtop

Um auf die auf dem Web-Server gespeicherten Daten zuzugreifen, gibt es drei Möglichkeiten. Zwei davon nutzen Streams, und zwar einmal über die Klasse URL und einmal über eine URLConnection. Bei der dritten Möglichkeit ist Handarbeit angesagt und fällt deshalb in das Kapitel über Sockets.

Jedes URL-Objekt besitzt die Methode openStream(), die einen InputStream zum Weiterverarbeiten liefert, sodass wir dort die Daten auslesen können:

InputStream in = uniURL.openStream();
final class java.net.URL
implements Serializable, Comparable

gp  final InputStream openStream() throws IOException
Öffnet eine Verbindung zur Server und liefert einen InputStream zurück. Diese Methode ist eine Abkürzung für openConnection().getInputStream().
gp  URLConnection openConnection() throws IOException
Liefert ein URLConnection Objekt, welches die Verbindung zum entfernten Objekt vertritt. openConnection() wird vom Protokoll-Handler immer dann aufgerufen, wenn eine neue Verbindung geöffnet wird.

Verweist die URL auf eine Textdatei, dann erweitern wir oft den InputStream zu einem BufferedReader, da dieser eine readLine()-Methode besitzt. Folgender Programmcode liest solange Zeilen, bis das Ende der Eingabe signalisiert wird. Glücklicherweise ist uns die Vorgehensweise schon bekannt, da sich ja das Lesen von einer Datei nicht vom Lesen eines entfernten URL-Objekts unterscheidet:

BufferedReader in = new BufferedReader(
new InputStreamReader( url.openStream() ) );

String line = "";

while ( (line = in.readLine()) != null )
System.out.println( line );

Sind die Daten gelesen, schließt close() den Datenstrom – close() bezieht sich allerdings nicht auf das URL-Objekt, sondern auf den Datenstrom. Es sei anschließend noch einmal ein vollständiges, lauffähiges Programm aufgeführt.

Listing 17.3   OpenURLStream.java
import java.net.*;
import java.io.*;

class OpenURLStream
{
public static void main( String args[] )
{
try
{
URL spiegelURL = new URL( "http://www.spiegel.de" );

BufferedReader in = new BufferedReader(
new InputStreamReader( spiegelURL.openStream() ) );

String s;

while ( ( s = in.readLine() ) != null )
System.out.println( s );

in.close();

} catch ( MalformedURLException e ) {
System.out.println( "MalformedURLException: " + e );

} catch ( IOException e ) {
System.out.println( "IOException: " + e );
}
}
}

Wir erzeugen ein URL-Objekt und rufen darauf die openStream()-Methode auf. Diese liefert einen InputStream auf den Dateiinhalt. In der API-Beschreibung wurde aber schon kurz erwähnt, dass diese Funktion eigentlich nur eine Abkürzung für openConnection().getInputStream() ist. openConnection() erzeugt ein URLConnection-Objekt und sendet diesem die Nachricht getInputStream().

Wir wollen uns im nächsten Abschnitt mit dem URLConnection-Objekt beschäftigen, denn damit wird die Verbindung über das Netzwerk zum Inhalt aufgebaut. Die URL-Klasse besitzt nur deshalb die Abkürzung über openStream(), da zum einen nicht jeder wissen muss, dass URLConnection dahinter steckt, und zweitens, weil es Tipperei erspart.

Das Beispiel zeigt auch, dass bei openConnection() ein try/catch-Block notwendig ist. Denn geht etwas daneben, zum Beispiel, wenn der Dienst nicht verfügbar ist, so wird eine IOException ausgelöst:

try {
URL ohoURL = new URL( "http://www.oho.com/" );
ohoURL.openConnection();
} catch ( MalformedURLException e ) { // new URL() ging daneben
...
} catch ( IOException e ) { // openConnection() schlug fehl
...
}

Galileo Computing

17.3 Die Klasse URLConnection  downtop

Die Objekte der Klasse URLConnection sind für den Empfang der Inhalte der URL-Objekte verantwortlich. Die Klasse ist abstrakt und die Unterklassen implementieren die Protokolle, mit denen die Verbindung zum Inhalt aufgebaut wird. Die Unterklassen bedienen sich dabei Objekten der Klasse URLStreamHandler, mit denen der eigentliche Inhalt ausgelesen wird. (Siehe hierzu Abbildung 17.2 auf der folgenden Seite.)

Abbildung


Galileo Computing

17.3.1 Methoden und Anwendung von URLConnection  downtop

Die Klasse URLConnection ist ein wenig HTTP-lastig, denn viele Methoden haben nur für URLs auf Web-Seiten eine Bedeutung. So stellt die Klasse Methoden bereit, um die Verbindung aufzubauen, den HTTP-Header zu lesen und den Inhalt eines Dokuments zu holen. Da eine Datei, die vom Web-Server kommt, den Inhalt (engl. Content) immer ankündigt (wenn wir später mit direkten Socket-Verbindungen arbeiten, lässt sich dies am zurückgeschickten Inhalt erkennen), so erkennt die Klasse URLConnection mit einem Content-Handler den Inhalt und bietet uns Benutzern Methoden an, um mit dem Header und den Inhalten zurechtzukommen.

Handelt es sich bei der Verbindung um das Protokoll HTTP, so schreibt die HTTP 1.1 Spezifikation im RFC 2616 einige Header vor, die durch spezielle Methoden der Klasse URLConnection abgefragt werden können.

Beispiel Um zu erfahren, wann die Datei auf dem Server gelandet ist, kann getDate() bzw. getLastModified() verwendet werden. Werfen wir dazu einen Blick auf die Methode printHeader().
void printHeader()
{
try
{
URLConnection c = connect.openConnection();
System.out.println( connect);
long d = c.getDate();
System.out.println( "Date : " + d);
Date dt = new Date( d );
System.out.println( " : " + dt);
d = c.getLastModified();
System.out.println( "Last Modified : " + d);
dt = new Date(d);
System.out.println( " : " + dt);
System.out.println( "Content encoding: " +
c.getContentEncoding());
System.out.println( "Content length : " +
c.getContentLength());
}
catch (Exception e) { System.out.println (e + ":" + connect); }
}

Die Methoden und Attribute von URLConnection

Die meisten der Attribute werden durch eine der Funktionen getHeaderField(), getHeaderFieldInt() oder getHeaderFieldDate() verarbeitet. getHeaderFieldInt() ist eine Hilfsfunktion und bedient sich getHeaderField() wie folgt: Integer.parseInt(getHeaderField(name)). Ebenso wandelt getHeaderFieldDate() mittels getHeaderField() den String in ein long um: return Date.parse(getHeaderField(name)). Schauen wir uns zwei der Methoden an:

public String getContentType() {
return getHeaderField("content-type");
}

public long getLastModified() {
return getHeaderFieldDate("last-modified", 0);
}

Wie nun getHeaderField() wirklich implementiert ist, können wir nicht sehen, da es sich dabei um Funktionen handelt, die von den Unterklassen überschrieben werden. Prinzipiell ist die URLConnection-Klasse zwar für alle Protokolle gleichwertig, doch an anderer Stelle wurde erwähnt, dass sie mehr zugunsten von HTTP entscheidet. Deshalb muss ein Rückgabewert von getLastModified() von einer FTP-Verbindung mit Vorsicht genossen werden.


Galileo Computing

17.3.2 Protokoll- und Content-Handler  downtop

Der Inhalt eines URL-Objekts lässt sich mit getContent() vom Server holen, falls ein passender Content-Handler eingetragen ist. Für Bilder ist etwa so ein Handler eingetragen, der als Rückgabewert ein URLImageSource liefert. Mit wenigen Zeilen können wir dann ein Bild in Form eines Image-Objekts erzeugen, das auf dem Server weilt:

public static Image fetchimage( 
String url )
throws MalformedURLException, IOException
{
URL u = new URL( url );
Toolkit tk = Toolkit.getDefaultToolkit();
return tk.createImage((ImageProducer)u.getContent());
}

Wenn wir konkret ein Bild über eine URL laden wollen, dann bietet sich sicherlich die Methode getImage(URL) an.

Mit getContent() an Daten zu gelangen, funktioniert für alle Objekte – natürlich muss ein passendes Protokoll installiert sein. Bei Content-Handlern gilt das Gleiche wie für Protokoll-Handler: Unterschiedliche Umgebungen implementieren unterschiedliche Handler. Für HTML-Dateien liefert getContent() ein Objekt vom Typ sun.net.www.MeteredStream zurück und für normale Textdateien ein sun.net.www.content.text.PlainTextInputStream Objekt; also nur Datenströme. Für Texte und HTML-Seiten können wir dann mit Hilfe des InputStreams (MeteredStream und PlainTextInputStream sind Unterklassen) die Datei zeilenweise auslesen. Leider gibt es keine Methode in der Bibliothek, die sofort die Daten in einem String bereitstellt.

Mit einer kleinen Zeile können wir erfragen, was für ein Handler-Objekt eine URL-Klasse für den Datenstrom einsetzt:

Object o = u.getContent();
System.out.println( "Schnapp: Ich habe einen " + o.getClass().getName() );

getContent() erkennt nun an Hand der Endung beziehungsweise der ersten Bytes den Typ der Datei. Dann konvertiert ein Content-Handler die Bytes seines Datenstroms in ein Java-Objekt. Der Protokoll-Handler überwacht die Verbindung zum Server und stellt dann die Verbindung zu einem konkreten Content-Handler her, der die Konvertierung in ein Objekt übernimmt.

Stellen wir zusammenfassend noch einmal den Content- und Protokoll-Handler gegenüber:

gp  Content-Handler: Durch einen Content-Handler wird die Funktionalität der URL-Klasse erweitert. Es können Quellen verschiedener MIME-Typen durch die Methode getContent() als Objekte zurückgegeben werden. Leider beschreibt die Java Spezifikation nicht, welche Content-Handler bereitgestellt werden müssen. Für GIFs und JPGs gibt es Handler, die gleich ImageProducer anlegen.
gp  Protokoll-Handler: Auch ein Protokoll-Handler erweitern die Möglichkeiten der URL-Klassen. Das Protokoll ist der erste Teil einer URL und gibt bei Übertragungen wie »http« die Kommunikationsmethode an. Auch hier gibt es keine verbindliche Verpflichtung, diese bei einer JVM auszuliefern. So unterstützt das JDK Protokolle wie »file«, »ftp«, »jar«, »mailto«, doch schon Netscape benutzt andere Implementierungen der Klasse URLConnection. Noch anders sieht es beim Microsoft Explorer aus. Also hilft nur das Selberprogrammieren .
final class java.net.URLConnection
implements Serializable, Comparable

gp  Object getContent() throws IOException, UnknownServiceException
Liefert den Inhalt, auf den die URL verweist. UnknownServiceException ist eine Unterklasse von IOException, es reicht also ein catch auf IOException aus.
final class java.net.URL
implements Serializable, Comparable

gp  final Object getContent() throws IOException
Liefert den Inhalt, auf den die URL verweist. Die Methode ist eine Abkürzung für openConnection().getContent().Wegen der Umleitung auf das URLConnection-Objekt kann auch hier eine UnknownServiceException auftauchen.

Galileo Computing

17.3.3 Im Detail: Von URL zu URLConnection  downtop

Die Klasse URLConnection ist abstrakt. Wird openStream() von einem URL-Objekt aufgerufen, so weiß diese Methode, wie die Verbindung zum Dienst aufzubauen ist. Denn für Web-Seiten mit dem HTTP-Protokoll sieht dies anders aus als eine Dateiübertragung mit dem FTP-Protokoll. openConnection() von URL macht nichts weiteres als vom jeweiligen Handler wiederum openConnection() aufzurufen. Die Handler wissen für ihr Protokoll, wie die Verbindung aufzubauen ist. Der Handler von URLConnection ist vom Typ URLStreamHandler, eine abstrakte Superklasse, die von allen Stream-Protokoll-Handlern implementiert wird. Leider können wir diese Implementierung nicht im Quelltext sehen. Im Konstruktor des URL-Objekts werden die Protokoll-Handler initialisiert. Denn an dieser Stelle ist klar, um was für einen Dienst es sich handelt. Ein URL-Parser zerpflückt dann die URL und ruft in dem Protokoll die getURLStreamHandler()-Methode auf. Sie würde null zurückliefern, falls sie mit dem Protokoll nichts anzufangen weiß – dies bekämen wir zu spüren, denn eine null heißt: MalformedURLException().

getURLStreamHandler()static synchronized gekennzeichnet – ist die eigentliche Arbeitsstelle. Hier wird zum Präfix sun.net.www.protocol. der Name des Handler gehängt (zum Beispiel ftp, http) und anschließend ein .Handler drangesetzt. Nun wird über Class.forName(clsName) nachgeschaut, ob die Klasse schon im System geladen wurde. Wenn nicht, dann versucht der Klassenlader über loadClass(clsName) an die Klasse zu kommen. Falls die Klasse geladen werden konnte, wird sie mit newInstance() initialisiert und als URLStreamHandler dem aufrufenden Konstruktor übergeben.

Soweit der Weg vom Konstruieren über ein URL-Objekt zum Laden des Handlers. Anschließend liegt der Handler URLStreamHandler vor und wird in der privaten Variablen handler abgelegt. Kommen wir nun noch einmal zur Methode openConnection(). Wir haben gesagt, dass diese Methode wissen muss, welches URLConnection-Objekt es zurückgibt, da das Protokoll bekannt ist. Und da das Protokoll vom Typ URLStreamHandler in der Variablen handler liegt, ist es ein einfaches, sich die openConnection()-Methode vorzustellen:

public URLConnection openConnection() 
throws java.io.IOException
{
return handler.openConnection(this);
}

Der Handler übernimmt selbst das Öffnen. Nun gibt es eine URLConnection und wir können damit auf die Referenz lesend (wir holen uns also Informationen beispielsweise von der Web-Seite) und schreibend (zum Beispiel für eine CGI-Abfrage) reagieren. Es muss betont werden, dass bei der Erzeugung eines URLConnection-Objekts noch keine Verbindung aufgebaut wird. Dies folgt mit den Methoden getOutputStream() oder getInputStream().

final class java.net.URLConnection
implements Serializable, Comparable

gp  URLConnection openConnection() throws IOException
Liefert ein URLConnection-Objekt, das die Verbindung zum entfernten Objekt vertritt. openConnection() wird vom Protokoll-Handler immer dann aufgerufen, wenn eine neue Verbindung geöffnet wird.

Galileo Computing

17.4 Das Common Gateway Interface  downtop

CGI (Common Gateway Interface) ist eine Beschreibung einer Schnittstelle http://cgi-spec.golux.com., mit der externe Programme mit Informations-Servern, meistens Web-Servern, Daten austauschen. Die aktuelle Version ist CGI/1.1. Diese ausgeführten Programme werden kurz »CGI-Programme« genannt und können in allen erdenklichen Programmiersprachen verfasst sein. Häufig sind es Shell- oder Perl-Skripte (oft wird dann die Bezeichnung CGI-Skripte verwendet). Die Unterscheidung zwischen Skript und Programm ist bei CGI schwammig. Traditionell ist eine compilierte Quelldatei ein Programm und Programme, die mit einem Interpreter arbeiten, ein Skript. Wir werden im Folgenden allerdings »Programm« und »Skript« austauschbar verwenden. Für uns ist es erst einmal egal, ob ein Programm oder Skript ausgeführt wird. Denn wir wollen diese Programme aus Java nutzen und nicht selber schreiben. Auf der Server-Seite ergänzen Servlets immer mehr CGI-Programme.

Die CGI-Programme werden von einem Browser durch eine ganz normale URL angesprochen. Der Browser baut eine Verbindung zum Server auf und dieser erkennt anhand des Pfads in der URL, ob es sich um eine ganz normale Web-Seite handelt oder um ein Skript. Wenn es ein Skript ist, dann führt der Server das Skript aus, welches eine HTML-Datei erzeugt. Diese wird übertragen und im Browser dargestellt. Der Aufrufer einer URL merkt keinen Unterschied zwischen erstellten, also dynamischen, und statischen Seiten. Die CGI-Programme sind also immer eine Angelegenheit des Servers, der uns mit aktuellen Daten versorgt.


Galileo Computing

17.4.1 Parameter für ein CGI-Programm  downtop

Beim Aufruf eines CGI-Programms können Parameter übergeben werden, bei einer Suchmaschine etwa der Suchbegriff. Es gibt nun zwei Möglichkeiten, wie diese Parameter zum Skript kommen und somit vom Web-Server verarbeitet werden.

gp  Die Parameter (genannt auch Query-String) werden an die URL angehängt (GET-Methode). Das Skript liest die Daten aus der Umgebungsvariablen QUERY_STRING aus.
gp  Die Daten werden zur Standardeingabe des Web-Servers gesendet (POST-Methode). Das Skript muss dann aus dieser Eingabe lesen.

GET und POST unterscheiden sich auch in der Länge der übertragenen Daten. Bei vielen Systemen ist die Länge einer GET-Anfrage beschränkt auf 1024 Byte. Der Content-Type (application/x-www-form-urlencoded) ist für GET- und POST-Anfragen identisch.

Daten werden nach der GET-Methode verschickt

Die Daten sind mit dem CGI-Programmnamen verbunden und gehen beide zusammen auf die Reise. Der Anfragestring (Query-String) wird hinter ein Fragezeichen gesetzt, das et-Zeichen »&« trennt mehrere Anfragezeichenketten. Unter Java setzen wir einfach einen Befehl ab, indem wir ein neues URL-Objekt erzeugen und anschließend den Inhalt auslesen:

meineURL = new URL( "http", "...", 
"cgi-bin/trallala?tri" );

Das CGI-Skript holt sich seinerseits die Daten aus der Umgebungsvariable QUERY_STRING. Das folgende Kapitel zeigt, wie diese Abfrage-Zeichenketten komfortabel durch die Klasse URLEncoder zusammengebaut werden. Werfen wir jedoch erst einen Blick auf die Variablen.

Daten holen nach der POST-Methode

Die Klasse URLConnection bietet die schon bekannte Methode getOutputStream() an, die eine Verbindung zur Eingabe des CGI-Scripts möglich macht (POST-Methode):

// CGI-Script schickt die Daten 
zurück

urlout PrintStream = new PrintStream(
cgiConnection.getOutputStream());
urlout.println( data );
urlout.close();

Galileo Computing

17.4.2 Codieren der Parameter für CGI-Programme  downtop

Wenn aus einer HTML-Datei mit Formularen über Submit Daten an das CGI-Programm übermittelt werden, dann werden diese Daten kodiert. Dies liegt daran, dass viele Zeichen in URL nicht erlaubt sind. Betrachten wir daher folgenden Ausschnitt aus einer Web-Seite:

<FORM  METHOD="GET" ACTION="/cgi-bin/caller.cgi">
<P>Name:
<INPUT TYPE = "text" NAME = "name" VALUE = "">
<P>E-Mail:
<INPUT TYPE="text" NAME="email" VALUE = "">
<P>
<INPUT TYPE="submit" name="submit" >
</FORM>

Die Seite besitzt zwei Felder mit den Namen name und email. Dazu kommt noch ein Submit-Button, der, falls aktiviert, die Daten an das CGI-Programm caller.cgi weitergibt. Wenn wir die Felder mit irgendeinem Inhalt füllen und Submit drücken, sehen wir URL häufig in der Adressleiste des Browsers. Dort erscheint, ohne Zeilenumbruch, zum Beispiel:

http://oho.de/cgi-bin/caller.cgi?
name=Ulli+Ullenboom&email=ulliull@ulli.com&submit=Submit

Da in einer URL keine Leerzeichen erlaubt sind, werden sie durch Plus-Zeichen kodiert. Es gibt noch weitere Zeichen, die kodiert werden, so das Plus- oder das Gleichheitszeichen oder auch das Und-Symbol. Von diesen Zeichen wird die Hex-Repräsentation als ASCII übersendet, aus »Ulli + Radhia« wird dann »Ulli+%2B+Radhia«. Aus dem Plus wird %2B und aus den Leerzeichen ein Plus.

Neben der Textkodierung fällt noch auf, dass in der übermittelten Zeile jeder Feldname und das Feld mit seinen Feldinhalt übermittelt wird. Somit lässt sich leicht der Inhalt eines Felds heraussuchen, denn nach dem Feldnamen ist ein Gleichheitszeichen eingefügt. Das Ende der Inhalte ist durch ein Und-Zeichen gekennzeichnet.

Wollten wir einen String dieser Art zu einer URL zusammenbauen, um etwa eine Anfrage an ein Suchprogramm zu formulieren, dann müssen wir den String nicht codieren. Dies übernimmt die Java-Klasse URLEncoder.

Abbildung

Listing 17.4   URLEncodeTest.java
import java.io.*;
import java.net.*;

public class URLEncoderDemo
{
public static void main( String args[] )
{
PrintStream o = System.out;

o.println(URLEncoder.encode("String mit Leerezeichen"));
o.println(URLEncoder.encode("String%mit%Prozenten"));
o.println(URLEncoder.encode("String*mit*Sternen"));
o.println(URLEncoder.encode("String+hat+ein+Plus"));
o.println(URLEncoder.encode("String/mit/Slashes"));
o.println(URLEncoder.encode("String\"mit\"Gänsen"));
o.println(URLEncoder.encode("String:Doppelpunkten"));
o.println(URLEncoder.encode("String=ist=alles=gleich"));
o.println(URLEncoder.encode("String&String&String") );

o.println(URLEncoder.encode("String.mit.Punkten"));
}
}

Wir bekommen folgende Ausgabe:

String+mit+Leerezeichen
String%25mit%25Prozenten
String*mit*Sternen
String%2Bhat%2Bein%2BPlus
String%2Fmit%2FSlashes
String%22mit%22G%E4nsen
String%3ADoppelpunkten
String%3Dist%3Dalles%3Dgleich
String%26String%26String
String.mit.Punkten

Galileo Computing

17.4.3 Eine Suchmaschine ansprechen  downtop

Wir wollen nun direkt eine Suchmaschine ansprechen und so das Verhalten eines Anfrageprogramms nachbilden. Unser Programm sammelt dazu alle Suchbegriffe als Parameter auf der Kommandozeile, falls keine Parameter vorhanden sind, wird nach »Teletubbies on Tour« gesucht. Anschließend kodiert die Klasse URLEncoder den Suchstring und der Inhalt wird hinter die URL des CGI-Programms gehängt, noch getrennt durch ein Fragezeichen. Dieses wird als Anfrage für die Suchmaschine Google verpackt und weggeschickt. Anschließend wird in einer Schleife die HTML-Datei Zeilenweise ausgegeben.

Listing 17.5   GoogleSeeker.java
import java.io.*;
import java.net.*;

public class GoogleSeeker
{
public static void main( String args[] ) throws Exception
{
String s = "";


if ( args.length == 0 )
s = "Teletubbies On Tour";
else
for ( int i = 0; i < args.length; i++ )
s += args[i] + " ";

s.trim();
s = "q=" + URLEncoder.encode( s );

URL u = new URL( "http://www.google.com/search?" + s );

BufferedReader in = new BufferedReader(
new InputStreamReader( u.openStream() ) );

String line, response = null;

while ( (line = in.readLine()) != null )
response += line+"\n";

System.out.print( response );
}
}

Galileo Computing

17.5 Host-Adresse und IP-Adressen  downtop

Ziehen Pakete durch das Internet, so orientieren sie sich an der numerischen IP-Adresse. Diese ist für die meisten Menschen schwer zu behalten und daher findet oft der Host-Name Verwendung, um einen Rechner im Internet anzusprechen. Die Konvertierung von Host-Namen in IP-Adressen übernimmt ein Domain-Name-Server (DNS). Seine Funktionalität ist durch eine Java-Funktion nutzbar. Die zu nutzende Methode ist getHostName(). Die folgende Zeile ist eine gültige Programmanweisung, um zur IP-Adresse den Host-Namen zu erfragen:

String host;
host = InetAddress.getByName("123.234.123.3").getHostName();

Die Klasse InetAddress repräsentiert eine IP-Adresse. Das Objekt wird durch die Funktionen getLocalHost(), getByName() oder getAllByName() erzeugt.

Das folgende Programm liefert ein Bytefeld und gibt die Oktette der IP-Adresse aus. Eine Oktette ist der Internetbegriff für ein Byte.

Listing 17.6   MyDNS.java
import java.net.*;

public class MyDNS
{
public static void main( String args[] ) throws Exception
{
String adress = "java-tutor.com";

InetAddress inet = InetAddress.getByName( adress );

byte ip[] = inet.getAddress();

for ( int i = 0; i < ip.length; i++ )
{
if ( i > 0 )
System.out.print( "." );

System.out.print( ((int)ip[i]) & 0xff );
}

System.out.println();
}
}
Abbildung

Mit der getBytes()-Methode aus der Klasse InetAddress lässt sich leicht herausfinden, welches Netz die Adresse beschreibt. Für ein Multicast-Socket ist die Internetadresse ein Klasse-D Netz. Dieses beginnt mit den vier Bits 1110, hexadezimal 0xE0. Folgende Zeilen fragen dies für eine beliebige InetAddress ab:

InetAddress ia = ...

if ( (ia.getBytes()[0] & 0xF0) == 0xE0 ) { // Klasse D Netz
...
}

Für den speziellen Fall einer Multicast-Adresse bietet InetAdress auch die Methode isMulticastAddress() an.

IP-Adresse des lokalen Hosts

Auch dazu benutzen wir wieder die Klasse InetAdress. Sie besitzt die statische Methode getLocalHost().

Beispiel Ermitteln der eigenen IP-Adresse Listing 17.7   getLocalIP.java
import java.net.*;

class getLocalIP
{
public static void main( String args[] )
{
try {
System.out.println( "Host Name und Adresse: " +
InetAddress.getLocalHost());
}
catch( Exception e ) { System.out.println( e ); }
}
}

Das Programm erzeugt auf zwei Rechnern eine Ausgabe der folgenden Art:

Host Name und Adresse: schnecke/192.10.10.2
Host Name und Adresse: lisa/127.0.0.1
class java.net.InetAddress
implements Serializable

gp  String getHostName()
Liefert den Host-Namen.
gp  String getHostAddress()
Liefert die IP-Adresse als String im Format »%d.%d.%d.%d«.
gp  static InetAddress getByName( String ) throws UnknownHostException
Liefert die IP-Adresse eines Hosts aufgrund des Namens. Der Host-Name kann als Maschinenname (»www.uni-paderborn.de«) oder numerische Repräsentation der IP-Adresse (»206.26.48.100«) beschrieben sein.
gp  InetAddress getLocalHost() throws UnknownHostException
Liefert ein IP-Adressen-Objekt des lokalen Hosts.

Galileo Computing

17.5.1 IPv6 für Java mit Jipsy  downtop

Bis zur Javaversion 1.4 unterstützt die Javabibliothek nur IPv4, also Netzwerkadressen, die sich durch vier Oktetten auszeichnen. Anfang 1970 fanden die Entwickler 32 Bit IP-Adressen mehr als genug, doch es reichte nicht. Mit IPv6, auch bekannt als IPng (IP Next Generation), existiert eine Erweiterung des IP-Verkehrs, sodass IP-Adressen nicht mehr den Engpass darstellen. Für IPv6 hat die Arbeitsgruppe voll zugelangt und 128 Bits eingeplant.

Ist für die älteren Javaversionen IPv6 gefragt, dann füllt die Software Jipsy von Matthew Flanagan in die Lücke. Seine Implementierung ersetzt die IPv4-lastigen Klassen im java.net-Paket einfach durch neue. Ohne Änderung des Programmcodes und Einbeziehung einer externen nativen Bibliothek ist der Schritt leicht gemacht. Dennoch lässt Jipsy die neue Schreibweise für Adressen zu. Während bei IPv4 die vier Bytes durch Punkte getrennt wurden, entstehen nun Adressen folgenden Formats:

4B23:12FA:667C:3621:9819:00A1:0044:CAFE

Eine IPv6-Adresse besteht aus acht Gruppen von jeweils vier Hexadezimalzeichen.

Die letzte Version von Jipsy lässt sich unter http://sourceforge.net/projects/jipsy beziehen. Nach dem Auspacken erhalten wir die Klassen und den Quellcode. Jipsy ist OpenSource, genauer gesagt, es steht unter GNU Lesser General Public License (LGPL). Flanagan implementiert die Klassen direkt ins Paket java.net, sodass wir der Laufzeitumgebung nur noch über den CLASSPATH die neuen Klassen mitteilen müssen. Zudem muss natürlich auch auf die native Bibliothek verwiesen werden, unter Unix, indem wir den Pfad zu libnet6.so zur Variablen LD_LIBRARY_PATH hinzufügen. Aus der beigelegten Datei install sind weitere Informationen zu entnehmen.

Die Implementierung ist natürlich plattformabhängig und so lässt sich nicht auf jeder Plattform IPv6 nutzen. Voraussetzung ist selbstverständlich, dass das Betriebssystem die nächste Generation nutzen kann. Der Quellcode compiliert zurzeit unter Linux Kernel 2.2.10 oder höher.


Galileo Computing

17.6 Socket-Programmierung  downtop

Die URL-Verbindungen sind schon High-Level Verbindungen, wir müssen uns nicht erst um Übertragungsprotokolle wir 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.


Galileo Computing

17.6.1 Das Netzwerk ist der Computer  downtop

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. Das ARPA (Advanced Research Projects Agency) gab der Universität von Berkeley (Californien) den Auftrag, unter UNIX das TCP/IP-Protokoll zu implementieren, um dort in dem Netzwerk zu kommunizieren. Was sich die Californier 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 entwickelt und ist der de facto-Standard für TCP/IP Kommunikation. So 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 und 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. 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 sie eine Zimmernummer im Hotel. Die Adresse bleibt dieselbe, aber in jedem Zimmer sitzt einer und macht seine Aufgaben.

Für jeden Dienst (Service), den ein Server zur Verfügung stellt, gibt es einen Port. Diese Adressen sind aber nicht willkürlich vergeben, sondern werden von der IANA (Internet Assigned Numbers Authority) vergeben. IANA geht aus der ISOC (Internet Society) und der FNC (Federal Network Council) hervor. Die Internet Authority ist der zentrale Koordinator für die IP-Adressen, Domain-Namen, MIME-Typen und vieler anderer Parameter, unter anderem auch der Port-Nummern – Näheres unter www.iana.org.

Eine Port-Nummer ist eine 16 Bit Zahl und in die Gruppen System und Benutzer eingeteilt. Die so genannten Well-Known System Port- (auch Contact Port-)Nummern 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. Die folgende Tabelle zeigt einige wenige dieser Port-Nummern. Die komplette Liste ist unter http://www.iana.org/assignments/port-numbers verfügbar.

Tabelle 17.2   Einige ausgewählte System-Ports
Service Port Beschreibung
echo 7 Echo
daytime 13 Daytime
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
www-http 80 World Wide Web HTTP
pop2 109 Post Office Protocol – Version 2
pop3 110 Post Office Protocol – Version 3
pwdgen 129 Password Generator Protocol

Die auf einem UNIX-System installierten Ports sind meistens unter /etc/services einzusehen.


Galileo Computing

17.6.2 Standarddienste unter Windows nachinstallieren  downtop

Wir wollen nun mit der Netzwerkprogrammierung bei den Clients beginnen. Diese greifen auf einen Server zu und nutzen dessen Dienste. Ein solcher Dienst wäre etwa ein Zeitserver oder ein Echoserver. Der Zeitserver liefert die aktuelle Tageszeit und ein Echoserver würde alle gesendeten Zeichenketten wieder zurückschicken. Wenn wir allerdings einen Dienst auf einem Rechner nutzen wollen, dann muss dieser auch angeboten werden. Unter Unix gibt es eine ganze Menge von Standarddiensten. Die Leser, die allerdings mit Windows arbeiten, haben überhaupt keine Dienste (ein Portscann zeigt dies leicht). Um Unix-Leser und Windows-Leser auf den gleichen Stand zu bringen, wollen wir unter Windows zwei Standarddienste installieren. Diese sind unter Windows abhängig von WinSock (http://www.winsock.com). WinSock ist die Abkürzung für Windows Socket und wird immer dann benötigt, wenn ein Computer in einem lokalen Netzwerk und über Modem ans Internet soll. Eine manuelle Installation von WinSock ist nicht mehr nötig; TCP/IP sollte aber auf dem Rechner installiert sein. Da sich viele Rechner jedoch im Internet oder im Netz befinden, ist dies vermutlich die Standardkonfiguration. Uns interessieren aber die Dienste. Sie bauen auf WinSock auf und unter der Web Seite »WinSock Development Information« finden wir unter Sample Applications einen Verweis (ftp://ftp.sockets.com/sockets/wsa_all.zip) auf das Paket mit den Quellcodes. Dieses Paket ist allerdings recht groß (1,29 MB) und enthält mehr als wir benötigen. Daher habe ich die ausführbaren Programme unter http://java-tutor.com/download/internet/WindowsSocketsBinaries.zip abgelegt. Das Archiv hat eine Größe von 196 KB.


Galileo Computing

17.6.3 Stream-Sockets  downtop

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 Datagramsockets, nicht der Fall. Wir behandeln die Stream-Sockets zuerst.

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 Netzwerk-Klassen 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 Socket-Klasse aufgebaut:

Socket clientSocket = new Socket( 
"die.weite.welt", 80 );

Der erste Parameter des Konstruktors ist der Name des Servers (Host-Adresse), mit dem wir uns verbinden wollen. Der zweite Parameter (optional) ist der Port, wir haben hier 80 gewählt.

Verbinden wir ein Applet mit dem Server, von dem es geladen wurde, würden wir mit getCodeBase().getHost() arbeiten.

Socket server = new Socket( getCodeBase().getHost(), 
7 );

Es gibt noch eine andere Möglichkeit, zu einem Host zu gelangen: Über die Klasse Inet Adress.

secondSocket = new Socket( server.getInetAdress(), 
1234 );

Alternativ ermittelt die Funktion getHostByName(String) die InetAdresse eines Hosts. Ist der Server nicht erreichbar, so wirft das System bei allen Socket-Konstruktionsversuchen eine UnknownHostException aus.

class java.net.Socket

gp  Socket() throws IOException
Erzeugt einen nicht verbundenen Socket. Es ist der Standard SocketImpl.
gp  Socket( SocketImpl impl ) throws IOException
Erzeugt einen unverbundenen Socket mit einer benutzerdefinierten SocketImpl.
gp  Socket( String host, int port ) throws IOException
Erzeugt einen Stream-Socket und verbindet ihn mit der Port-Nummer am angegebenen Host.
gp  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.
gp  Socket( String host, int port, InetAddress localAddr, int localPort )
throws IOException
Erzeugt einen Socket und verbindet ihn zum Host am Port.
gp  Socket( InetAddress address, int port,.InetAddress localAddr, int localPort )
throws IOException
Erzeugt einen Socket und verbindet ihn zum Host am Port.

Die Verbindung wieder abbauen

Die Methode close()leitet das Ende einer Verbindung ein. Andernfalls reserviert das Betriebssystem keine Handles und weiteres Arbeiten ist unter Umständen nicht mehr 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

gp  void close() throws IOException
Schließt den Socket.

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 Ausgabe-Strom vom Typ OutputSteam:

OutputStream out = server.getOutputStream()

Oft wird dieser dann noch schnell zu einem DataOutputStream gemacht, damit die Ausgabemöglichkeiten vielfältiger sind. Genauso verfahren wird mit dem Eingabestrom. Wandeln wir ihn gleich in einen BufferedReader um:

BufferedReader in = new BufferedReader(
new InputStreamReader( server.getInputStream() ));
Abbildung

class java.net.Socket

gp  InputStream getInputStream() throws IOException
Liefert den Eingabestrom für den Socket.
gp  OutputStream getOutputStream() throws IOException
Liefert den Ausgabestrom für den Socket

Galileo Computing

17.6.4 Informationen über den Socket  downtop

Genauso wie beim URL-Objekt, lassen sich auch beim Socket keine grundsätzlich wichtigen Parameter nachträglich ändern. So muss etwa die Port-Adresse wie das Ziel beim Erzeugen bekannt sein. Aber wie bei einer URL auch, lassen sich Informationen über das Socket-Objekt einholen.

final class java.net.Socket

gp  InetAddress getInetAddress()
Liefert die Adresse, zu dem der Socket verbunden ist.
gp  InetAddress getLocalAddress()
Liefert die lokale Adresse, an den der Socket gebunden ist.
gp  int getPort()
Gibt den remote-Port zurück, mit dem der Socket verbunden ist.
gp  int getLocalPort()
Gibt den lokalen Port des Sockets zurück.

Galileo Computing

17.6.5 Mit telnet an den Ports horchen  downtop

Wir können zum Server mittels telnet eine Verbindung aufbauen. Die Signatur von telnet ist:

telnet IP Port

Mit dieser Technik können wir uns zum Beispiel direkt mit dem FTP-Server verbinden, ohne ein Frontend wie ftp zu nutzen oder auch ein echo-Kommando absetzen und damit den Server testen.


Galileo Computing

17.6.6 Ein kleines Ping – lebt der Rechner noch?  downtop

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.

Hinweis Ein solcher Echo-Server ist oft unter Unix aktiv, unter Windows und anderen Systemen jedoch nicht. Wir können daher nur zu einem Rechner die Verbindung aufbauen und unser Programm prüfen, wenn dieser Dienst auch tatsächlich läuft. Wenn wir lokal auf einem Windows-Rechner arbeiten, dann können wir uns einige primitive Dienste durch Hilfsprogramme installieren. Die Quelle wurde oben schon genannt und ist http://java-tutor.com/download/internet/WindowsSocketsBinaries.zip. Läuft der Echo-Dienst unter einem Rechner nicht, dann wird eine »java.net.ConnectException: Connection refused: no further information« Ausnahme angezeigt.

Abbildung Wenn wir den Ping-Dienst nutzen wollen, dann senden wir ein Testwort zum Server und überprüfen, ob das gleiche Wort wieder zurückkommt. Die Verbindung zum Echo-Server herzustellen 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 lässt sich der InputStream und OutputStream holen und die Anfrage schicken. Die IP-Adresse lesen wir von der Kommandozeile.

Listing 17.8   Ping.java
import java.io.*;
import java.net.*;

class Ping
{
public static void main( String args[] )
throws Exception
{
Socket t = new Socket( args[0], 7 );

BufferedReader in = new BufferedReader(
new InputStreamReader( t.getInputStream()) );

PrintStream os = new PrintStream( t.getOutputStream() );

String test = "Superkalifragilistischexpialigetisch";

os.println( test );

String s = in.readLine();

if ( s.equals(test) )
System.out.println( "Hurra, er lebt!" ) ;
else
System.out.println( "Oh, er ist tot!" );

t.close();
}
}

Galileo Computing

17.7 Client/Server-Kommunikation  downtop

Bevor wir nun weitere Dienste untersuchen, wollen wir einen kleinen Server programmieren. Server bauen keine eigene Verbindung auf, sondern horchen an ihrem zugewiesenen Port auf Eingaben und Anfragen. Ein Server wird durch die Klasse ServerSocket repräsentiert. Da wir einen Server selber programmieren wollen, erzeugen wir ein ServerSocket-Objekt mit einem Konstruktor, dem wir einen Port als ersten Parameter übergeben:

ServerSocket serverSocket = new 
ServerSocket( 1234 );

So richten wir einen Server ein, der am Port 1234 horcht. Natürlich müssen wir unserem Client eine noch nicht zugewiesene Port-Adresse zuteilen, andernfalls ist uns eine IOException sicher. Das häufig verwendete 1234 ist zwar schon vom Infoseek Search Agent (search-agent) zugewiesen, sollte aber dennoch nicht zu Problemen führen, da er auf dem eigenen Rechner gewöhnlich nicht installiert ist.

Abbildung

Nachdem der Socket eingerichtet ist, kann er auf hereinkommende Meldungen reagieren. Mit der Methode accept() der ServerSocket-Klasse nehmen wir genau eine wartende Verbindung an:

Socket server = serverSocket.accept();

Nun können wir mit dem zurückgegebenen Client-Socket genauso verfahren wie mit dem schon programmierten Client. Das heißt, wir öffnen Ein- und Ausgabe-Kanäle und kommunizieren.

Wichtig bleibt zu bemerken, dass die Konversation nicht über den Server-Socket selbst läuft. Dieser ist immer noch aktiv und horcht auf eingehende Anfragen. Die accept()-Methode sitzt daher oft in einer Endlosschleife und erzeugt für jeden Hörer einen Thread. Die Schritte, die also jeder Server vollzieht sind Folgende:

1. Ein Server-Socket erzeugen, der horcht

Der Server wartet auch nicht ewig

Soll der Server nur eine gewisse Zeit auf einkommende Nachrichten warten, so lässt sich ein Timeout einstellen. Dazu ist der Methode setSoTimeout() die Anzahl der Millisekunden zu übergeben. Nimmt der Server dann keine Fragen entgegen, bricht die Verarbeitung mit einer InterruptedIOException ab. Diese Exception gilt für alle Ein- und Ausgabe-Operationen und ist daher auch eine Ausnahme, die nicht im Net-Paket, sondern im IO-Paket deklariert ist:

ServerSocket server = new ServerSocket( 
port );

// Timeout nach 1 Minute
server.setSoTimeout( 60000 );
try {
Socket socket = server.accept();
} catch ( InterruptedIOException e ) {
System.err.println( "Timed Out after one minute" );
}

Galileo Computing

17.7.1 Ein Multiplikations-Server  downtop

Der erste Server, den wir programmieren wollen, soll zwei Zahlen multiplizieren. Dazu reichen wir ihm im Eingabestrom zwei Zahlen, die er dann multipliziert und wieder schreibt.

Listing 17.9   Server.java
import java.net.*;
import java.io.*;

class Server
{
static void init() throws IOException
{
ServerSocket server = new ServerSocket( 3141 );

while ( true )
{
Socket client = server.accept();

InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();

int start = in.read();
int end = in.read();

int result = start * end;
out.write( result );
}
}

public static void main( String args[] )
{
try {
init();
} catch ( IOException e ) { System.out.println( e ); }
}
}

Wir starten den Server auf Port 3141. Nun geht es auf der anderen Seite mit dem Client weiter:

class Client
{
static void init() throws IOException
{
Socket server = new Socket ( "localhost", 3141 );

InputStream in = server.getInputStream();
OutputStream out = server.getOutputStream();

out.write( 4 );
out.write( 9 );

int result = in.read();
System.out.println( result );

server.close();
}

public static void main( String args[] )
{
try {
init();
}
catch ( IOException e ){ System.out.println( "Error " + e );}
}

Galileo Computing

17.8 Webprotokolle mit NetComponents nutzen  downtop

Daniel F. Savarese von Original Reusable Objects hat die Bibliothek NetComponents (http://www.savarese.org/oro/software/NetComponents.html) entwickelt, die einfachen Zugriff auf die wichtigsten Applikationsprotokolle bietet. Seine Bibliothek in der Version 1.3.8 unterstützt auf nachfolgenden Protokolle:

gp  FTP (File Transfer Protocol)
Dient zur Dateiübertragung von und zu jedem beliebigen Rechner im Internet.
gp  TFTP (Trivial File Transfer Protocol)
Einfache Variante von FTP ohne Sicherheitsprüfung.
gp  NNTP (Network News Transfer Protocol)
Protokoll zum Versenden und Empfangen von Nachrichten in Diskussionsforen.
gp  SMTP (Simple Mail Transfer Protocol)
Standardprotokoll, mit dem E-Mails auf einen Server übertragen werden.
gp  POP3 (Post Office Protocol, Version 3)
Bisheriges Standardprotokoll, mit der E-Mails vom Server abgeholt werden.
gp  Telnet (Terminalemulation)
Bietet die Möglichkeit, sich in spezielle Rechner einzuloggen.
gp  Finger, Whois
Informations- und Nachschlagedienste, um Informationen über Personen einzuholen.

Daneben unterstützt die Bibliothek auch die BSD-R-Kommandos, wie rexec, rcmd/rshell und rlogin. Für die Zukunft ist die Unterstützung von ONC RPC, DNS geplant.

In dem Paket von Savarese finden sich die Klasse und einige Beispielprogramme. Bisher ist die Bibliothek nicht Open-Source, aber jeder kann sie frei verwenden.


Galileo Computing

17.9 E-Mail verschicken  downtop

E-Mail (Abk. für elektronische Mail, kurz: Mail) ist heute ein wichtiger Teil der modernen Kommunikation. Während es in der akademischen Welt unentbehrlich ist, setzt es sich auch in der kommerziellen Welt durch. Die Vorteile bei der Technik sind vielfältig: Das Medium ist schnell, die Nachrichtenübertragung ist asynchron und die Informationen können direkt weiterverarbeitet werden; ein Vorteil, der bei herkömmlichen Briefen nicht auszunutzen ist.


Galileo Computing

17.9.1 Wie eine E-Mail um die Welt geht  downtop

In einem E-Mail-System kommen mehrere Komponenten vor, die kurz vorgestellt werden sollen:

gp  User
Der Benutzer des Mailsystems, der Nachrichten verschickt oder empfängt.
gp  Mail User Agent (MUA)
Die Schnittstelle zwischen dem Benutzer und dem Mailsystem.
gp  Message Store (MS)
Dient dem Mail User Agent zum Ablegen der Nachrichten.
gp  Message Transfer Agent (MTA)
Dies sind Komponenten des Message Transfer Systems und verantwortlich für die eigentliche Nachrichtenübermittlung.
gp  Message Transfer System (MTS)
Ist die Gesamtheit der Message Transfer Agents und ihrer Verbindungen.

Die Nachrichtenübermittlung läuft also über diese Komponenten, wobei der Benutzer durch sein Mail User Agent (MUA) die Nachricht erstellt. Anschließend wird diese Nachricht vom MUA an den Message Transfer Agent übergeben. Nun ist es Aufgabe dieser Agenten, entweder direkt (der Empfänger ist im gleichen Netzwerk wie der Sender) oder über Store-and-Forward die Botschaft zu übermitteln. Danach ist der Sender aus dem Spiel und der Empfänger nimmt die E-Mail entgegen. Sie wird vom Ziel MTA an den Mail User Agenten des Abrufenden geholt.


Galileo Computing

17.9.2 Übertragungsprotokolle  downtop

Für die Übertragung von E-Mail haben sich zwei Protokolle etabliert: X.400 und SMTP. Während SMTP breite Akzeptanz – besonders in der akademischen Welt genießt – und es viele Implementierungen und Benutzungsoberflächen gibt, ist X.400 kommerziell gefälliger. Es ist in Standardisierungsgremien anerkannt und bietet Möglichkeiten, die über SMTP hinausgehen. Diese Dienste werden für SMTP erst durch neue Spezifikationen wie MIME möglich.

Das Simple Mail Transfer Protokoll und RFC 822

Das Simple Mail Transfer Protokoll (kurz SMTP) ist ein Versende-Protokoll, welches im RFC 821 beschrieben ist. Zusammen mit dem RFC 822 (Standard for the format of ARPA Internet Text Messages), welches den Austausch von textbasierten Nachrichten im Internet beschreibt, bildet dies das Gerüst des Mailsystems. Über RFC 822 wird nur der Aufbau der Nachrichten beschrieben – es ist kein Protokoll – und im RFC 821 das Protokoll zum Verschicken und Übertragen der Nachrichten von einem Rechner zum anderen. Es ist wichtig zu betonen, dass beide völlig unabhängig arbeiten (nicht so wie X.400) und dass zum Beispiel der Absender und Adressat im Header der E-Mail zwar den Empfänger sieht, jedoch nicht der SMTP-Server. Beide Systeme sind schon sehr alt, das Simple Mail Transfer Protokoll stammt ebenso wie der Standard für die ARPA Internet Text Nachrichten aus dem Jahr 1982. Was früher noch genügte, wird heute immer mehr zum Hindernis, da die kommerzielle (böse) Welt ganz andere Weichen stellt.

SMTP verwendet den Standard RFC 822 zur Schreibweise der E-Mail Adressen. Diese Adressierung verwendet den Rechnernamen (bzw. Netznamen) des Adressaten. Mit dem leicht zu merkenden Fully Qualified Host Name (kurz FQHN) lassen sich die E-Mails leicht verteilen. Denn jeder Rechner ist im DNS aufgeführt, und wenn der Name bekannt ist, lässt sich somit also leicht zum Empfänger routen. So macht es zum Beispiel das Programm sendmail. Es baut eine direkte Verbindung zum Zielrechner auf und legt die Nachricht dort ab. Da allerdings gerne eine Unabhängigkeit von real existierenden Rechnern erreicht werden soll werden in der Regel MX-Records eingesetzt. Somit tritt ein anderer Rechner als Mail Transfer Agent auf und so ist Erreichbarkeit für die Rechner gewährleistet, die nicht am Internet angeschlossen sind. Außerdem können auch mehrere Rechner als MTAs definiert werden, um die Ausfallsicherheit zu erhöhen. Zudem haben MX-Records den Vorteil, dass auch (einfachere) E-Mail Adressen formuliert werden können, für die es keinen Rechner gibt.

Die Einteilung der Domains erfolgt in organisatorische Toplevel-Domains. Einige sind:

gp  .com: Kommerzielle Organisationen
gp  .edu: Ausbildende Institutionen
gp  .gov: Regierung und Staatsapparat
gp  .net: Netzwerkunterstützende Organisationen
gp  .mil: Militärische Organisationen
gp  .org: Der ganze Rest

Diese Toplevel-Domains sollen auf kurz oder lang erweitert werden. Allerdings streiten sich hierüber noch die amerikanische Regierung und die Anbieter. Sowieso ist dies alles eine ziemlich amerikanische Angelegenheit. Für jedes Land gibt es dann noch einmal eine Sonderendung, wie das ».de« für Deutschland. Die Adresse des Autors ist zum Beispiel C.Ullenboom@Java-Tutor.com.

Im RFC 822 sind zwei von Grund auf unterschiedliche Adressierungsarten beschrieben. Allerdings wird nur eine davon offiziell unterstützt, und zwar die Route-Adressierung. Dies ist eine Liste von Domains, über die die E-Mail verschickt werden soll. Der Pfad des elektronischen Postmännchens wird also hier im Groben vorgegeben. Die zweite Adressierung ist die Local-Part-Adressierung. Hier wird ein spezieller Rechner angesprochen.

Multipurpose Internet Mail Extensions (MIME)

Als das Internet in den ersten Zügen war, bestand eine E-Mail meistens aus lesbaren englischen Textnachrichten. Wenn denn einmal binäre Dateien verschickt werden sollten, so mussten diese vom Benutzer unter UNIX mit einem uuencode in 7 Bit ASCII umgewandelt und dann verschickt werden. Der Standard des RFC 822 kann die heute anzutreffenden Daten wie

gp  Binärdaten (z. B. Audiodaten, Bilder, Video, Datenbanken)
gp  Nachrichten in Sprachen mit Umlauten und Akzenten (Deutsch, Französisch)
gp  Nachrichten in nichtlateinischen Sprachen (Hebräisch, Russisch) oder sogar Sprachen ohne Alphabet (Chinesisch, Japanisch)

nicht kodieren. Um Abhilfe zu schaffen wurde MIME (Multipurpose Internet Mail Extension) im RFC 1341 und RFC 1521 vorgeschlagen. Um ASCII fremde Nachrichten zu kodieren, werden fünf Nachrichtenheader definiert, und die Binärdateien nach base64 Encoding umgesetzt. Für Nachrichten, die fast nur aus ASCII-Zeichen bestehen, wäre dies aber zu großer Overhead, sodass Quoted Printing Encoding eingesetzt wird. Dies definiert lediglich alle Zeichen über 127 durch zwei hexadezimale Zeichen.

X.400

Die ISO und CCITT haben X.400 auf die Beine gestellt. Es ist ein Kurzname für eine Reihe von Standards, die im Gegensatz zum proprietären RFC offiziell verabschiedet sind. Die Entwicklung von X.400 ist in Studienperioden von jeweils vier Jahren eingeteilt. Es soll zum Quasistandard RFC 821/822 einen Gegenpol schaffen, was die Unterstützung der Telekommunikationsgesellschaften unterstreicht.

Die Schreibweise einer X.400-Adresse unterscheidet sich schon rein optisch von der Angabe des RFC 822. Hier wird eine Attributschreibweise (auch O/R-Adresse) verwendet, die eine Reihe von Attributen (sprich Attributtyp und Attributwert) angibt. Die Einteilung der Adressen erfolgt auf oberster Ebene in Länder und in der nächsten Stufe in administrative Bereiche. Dies kann zum Beispiel eine Telefongesellschaft sein.

Beispiel Format einer X.400 Adresse
G=ulli;S=ull; OU=rz; P=uni-paderborn; 
A=d400-gw; C=de

Der Countrycode bestimmt das Land. Der Attributname ist C und ist hier auf Deutschland gesetzt.

Die Umwandlung von X.400 in RFC 822 Format

Bekommt der Benutzer eine E-Mail-Adresse in X.400 Schreibweise, so muss diese jetzt in die RFC 822 Notation umgesetzt werden, wenn die Post mit einem SMTP-Mailer verwaltet wird. Auch dies ist das Thema von verschiedenen RFCs, namentlich RFC 987, 1026, 1138, 1148 und 1327. Es gibt zwar Standardabbildungen, jedoch gibt es zu viele Ausnahmen, als das sie hier beschrieben werden können.

Das System der Zukunft

Das heute noch vielfach eingesetzte SMTP bereitet den Anwendern viele Sorgen. So müssen sie sich mit dem Versand von gefälschten E-Mails herumschlagen (Mail-Spoofing), zudem ist unerbetene kommerzielle Werbung (Unsolicited Commercial E-Mail (UCE)) und UBE (Unsolicited Bulk E-Mail), besser bekannt als Spam, ein wirtschaftlich negativer Faktor. SMTP wird mit diesen Problemen nicht fertig, da der Einsatz für ein überschaubares Netz gedacht war, dessen Teilnehmer sich alle kennen. Der SMTP-Server ist so brav, dass er verpflichtet ist, jede E-Mail von jedem beliebigen Adressaten zu jedem beliebigen Absender zu schicken.

Da X.400 wirtschaftlich gut unterstützt wird und zudem einige Vorteile bietet, ist SMTP und RFC 822 auf dem absteigenden Ast. Einige der Stärken von X.400 sind:

gp  Anforderung der Auslieferungsbestätigung (Delivery Notification)
gp  Einlieferungsbestätigung (Proof of Submission)
gp  Das Mail Transport System liefert die Nachricht nicht später als zu einem angegebenen Zeitpunkt aus (Latest Delivery Designation)
gp  Der Empfänger authentifiziert die Nachricht gegenüber dem Absender (Proof of Delivery)
gp  Der User Agent leitet bestimmte Nachrichten für eine spezielle Zeit an einen anderen User Agent weiter (Redirection of Incoming Messages)
gp  Die Nachricht wird nicht weitergeleitet, wenn der Emfänger die Nachrichten umgelenkt (Redirection if Incoming Messages) haben möchte (Redirection Disallowed by Orginator)

Das Senden von Empfangsbestätigungen lässt Sendmail mit einem speziellen Feld Return-Receipt-To zu. Dieses ist jedoch nicht offiziell im RFC 822 standardisiert.


Galileo Computing

17.9.3 Das Simple Mail Transfer Protocol  downtop

Die Idee eines E-Mail Programms ist einfach: Zunächst muss eine Verbindung zum richtigen E-Mail Server stehen und anschließend kann die Kommunikation mit ein paar Kommandos geregelt werden. Mit dieser Technik kann dann eine Mail empfangen und gesendet werden. Nun besteht das E-Mail System aber aus zwei Teilen. Zum einen aus dem Empfänger und zum anderen aus dem Sender. Die empfangende Seite nutzt einen POP3-Server, die sendende Seite einen SMTP-Server. Dazu muss dieser natürlich aktiv auf Nachrichten horchen. Er meldet, wenn etwas daneben geht, und der Empfänger nicht benachrichtigt werden konnte. POP3 ist die dritte Version des Post Office Protocol. Der POP3-Server sitzt auf der Empfängerseite und stellt die Infrastruktur bereit, damit die E-Mail auch eingesehen werden kann. Beide Systeme arbeiten also Hand in Hand. Wird mittels SMTP-Server eine E-Mail versendet, so kann sie durch den POP3-Server abgerufen werden. Wir schauen uns zuerst die SMTP-Seiten an.

Aufbau einer E-Mail

Zunächst sollten wir uns um den Aufbau der E-Mail kümmern. Sie besteht aus zwei Teilen, dem Envelope, der Informationen über die Weiterleitung enthält, und dem Content, also dem Inhalt. Der Content gliedert sich wiederum in Header (=Adresse) und Body (=Inhaltauf. Der Envelope enthält ein Feld From, in dem der Absender eingetragen ist. Der zweite Teil des Kopfes besteht aus einem To, mit dem der Empfänger angesprochen wird. SMTP arbeitet rein textbasiert und binäre Daten müssen in 7 -Bit-Form codiert werden. Ganz anders ist dies bei X.400. Hier sind alle Daten kodiert und im ASN.1-Format ist beschrieben, wie genau Envelope, Header und Body zu füllen sind. Durch die Kodierung lassen sich auch Daten verschicken, die nicht aus Text bestehen.

Verschicken einer E-Mail über Telnet

Bevor wir mit einer praktischen Bibliothek E-Mail versenden, gehen wir auf die unterste Protokollebene und schauen uns eine Telnet-Sitzung an. Anschließend erklären wir die einzelnen Befehle:

$ telnet mail 25
Trying 131.234.22.30...
Connected to uni-paderborn.de.
Escape character is '^]'.
220 uni-paderborn.de ZMailer Server 2.99.49p9 #1 ESMTP+IDENT\
ready at Wed, 6 May 1998 22:52:47 +0200
HELO max.uni-paderborn.de
250 uni-paderborn.de Hello max.uni-paderborn.de
MAIL FROM: <ulliull@hni.uni-paderborn.de>
250 Ok (verified) Ok
RCPT TO: <ulliull>
250 Ok (verified) Ok
DATA
354 Start mail input; end with <CRLF>.<CRLF>
Subject: Huh, endlich geschafft
Lass knacken Alter
.
250 Ok

Verbindung zum SMTP-Server

Telnet baut eine Verbindung zum SMTP-Server auf. In einem Java-Programm hieße dies, eine Socket-Verbindung zu einem SMTP-Server öffnen. (Dieser muss auf unserem Server installiert sein, falls es von einem Applet laufen soll). Dann einen OutputStream/InputStream zum SMTP-Server holen und anschließend die E-Mail (nach RFC 821/822) in den Stream schreiben.

Neben dem Namen des SMTP-Servers ist die Port-Nummer wichtig. 25 ist der Standard-Port, an dem die meisten SMTP-Servers arbeiten. Nach der Verbindung können wir nun Kommandos abschicken, die im RFC 821 beschrieben sind. Diese Kommandos sind der Schlüssel zur Kommunikation.

Das Ergebnis der Operation erfragen

Direkt nach dem erfolgreichen Anmelden haben wir eine Ausgabe ähnlich dieser:

220 uni-paderborn.de ZMailer Server 
2.99.49p9\
#1 ESMTP+IDENT ready at Wed, 6 May 1998 22:52:47 +0200

Wir erhalten eine Nachricht und jede Antwort (Reply) vom SMTP-Server beginnt mit einer Nummer. Dies sind die SMTP Server Reply Codes (beschrieben unter Kapitel 4.2.2. NUMERIC ORDER LIST OF REPLY CODES des RFCs 821). Diese Nummern haben dieselbe Bedeutung wie die Reply-Codes beim FTP. So bedeutet 200 alles OK, und 421 heißt: geht nicht. Neben den Reply-Codes werden auch Texte versendet. Diese sind aber nur »human readable«, also für uns Menschen gemacht.

Alle Reply-Codes lassen sich in drei Klassen einteilen: success (S), failure (F), and error (E). Diese Unterteilung haben wir auch schon bei FTP kennen gelernt.

Die Ameldung

Um weitere Operationen loszuschicken, müssen wir uns erst brav anmelden: So senden wir nach dem Anmelden ein HELO max.uni-paderborn.de. Halten wir uns nicht an die Abmachung, melden einige Server »Polite users say HELO first«. Da die Mails über DNS geroutet werden, verlangt SMTP keine Authentifizierung. Zwar ist beim HELO der Name des sendenden Rechners anzugeben, dieser Name muss nicht korrekt sein und ein Passwort wird auch nicht verlangt. Also könnte grundsätzlich jeder eine beliebige Adresse (sowohl im Envelope als auch im Header) angeben (forging). Neuere Versionen von Sendmail können allerdings feststellen, woher die Verbindung wirklich kommt. Dabei wird das Sprungbrett TCP/IP benutzt.

Nun liefert der Server eine Antwort (die von einem Programm dann geholt werden müsste). Er liefert etwa bei unserem HELO

250 uni-paderborn.de Hello max.uni-paderborn.de

Hier ist es wichtig, dass der Reply Code mit 250 beginnt. Eine andere Begrüßung ist etwa »Pleased to meet you pc19f8f43.dip.t-dialin.net« bei T-Online.

Beschäftigen wir uns an dieser Stelle noch etwas mit den Kommandos. Unser erstes Kommando ist HELO und natürlich gibt es noch einige mehr. Alle Befehle bestehen aus vier Buchstaben (four-letter commands). Sie sind hier in Großbuchstaben geschrieben, müssen aber nicht. Wir machen dies nur zur Unterscheidung. Der SMTP-Server erkennt die Kommandos auch bei gemischter Groß/Kleinschreibung (also anstatt HELO, auch helo oder HeLo). Dies ist bei FTP oder dem POP3-Server, den wir noch kennen lernen werden, auch so.

Die Identifizierung

Bevor die E-Mail gesandt wird, informieren wir den Server über uns. Dies geschieht mit dem Kommando MAIL FROM. So zum Beispiel in folgendem Codestück:

MAIL FROM: <Ulli@java-tutor.com>

Hier sollte Ulli@java-tutor.com natürlich durch die eigene Adresse ersetzt werden (wenn wir auch der Sender sind). Der String sollte immer die Zeichen < und > enthalten. Alte Mail-Server erlauben auch keine eckigen Klammern zu setzen, mittlerweile sind diese jedoch Pflicht.

Nach dieser Identifizierung sollte der Server anerkennend mit 250 antworten, etwa

250 Ok (verified) Ok

oder

250 ... Sender ok

Den Absender bestimmen

Nun weiß der Server, von wem die E-Mail kommt, und der Empfänger bzw. die Empfänger können angegeben werden. Für jeden Empfänger werden die Zeilen

RCPT TO: Empfänger

angelegt. Wir können jedem, der eine E-Mail Adresse besitzt, eine Nachricht schicken. Dann sollte die Adresse in recipient stehen. Die Antwort, die der Server dann liefert, ist hoffentlich wieder mit dem Reply-Code 250 (alles OK).

250 whomever@wherever.com... Recipient 
ok

Im oberen Beispiel war ich selbst der Empfänger – so belästigen wir durch unsere Spielerei keine Freunde.

Die Daten

Nachdem Absender und Empfänger angegeben sind, können jetzt die Daten spezifiziert werden. Dies funktioniert aber wirklich nur dann, wenn mindestens ein Empfänger vom SMTP-Server als korrekt erkannt wurde. Der Körper der Nachricht folgt hinter einer DATA-Anweisung. Sie verlangt keine weiteren Angaben. Direkt danach sollte DATA vom Server eine Botschaft der Form

354 Enter mail, end with "." on 
a line by itself

entlocken. Jetzt kann die Nachricht verschickt werden. Sie endet mit einem Punkt in einer einzelnen Zeile.

In den oberen Zeilen liegt eine kleine Schwierigkeit, denn der Server produziert so lange keine Meldungen, bis die Nachricht abgeschlossen ist. Wir müssen also drauf hoffen, dass alles gut läuft und dass nach dem Abschluss der Server sein OK gib. Dies bedeutet, dass die Mail gesendet wurde. Eine explizites »Jetzt losschicken« gibt es nicht. Nun können weitere MAIL FROM:-Anweisungen kommen.


Galileo Computing

17.9.4 Demoprogramm, welches eine E-Mail abschickt  downtop

Nachdem wir alles mehr theoretisch gehandhabt haben, soll nun eine kleines Programm folgen, mit dem sich eine E-Mail abschicken lässt. Das Programm ist bewusst klein. Für komplexere E-Mail Anwendungen ist die spezielle nicht einfache Klassenbibliothek von Sun zu empfehlen, die JavaMail-API. Sie lässt sich von http://java.sun.com/products/javamail beziehen. Beispiele finden sich unter anderem auf den Seiten von http://www.javaworld.com.

Listing 17.10   EMail.java
import java.io.*;
import java.net.*;
import java.util.*;

public class EMail
{
public static void main( String args[] )
{
try
{
Socket mailServer = new Socket( "<Mail Server>", 25 );

BufferedReader in = new BufferedReader(
new InputStreamReader( mailServer.getInputStream()) );

PrintWriter out = new PrintWriter(
mailServer.getOutputStream(), true );

System.out.println("Host -> " + in.readLine() );

out.println( "HELO " + "<Mailserver>" );
System.out.println( "Host -> " + in.readLine() );

out.println( "MAIL FROM: " + "<Unsere Adresse>");
System.out.println( "Host -> " + in.readLine() );

out.println( "RCPT TO: " + "<Empfängeradresse>" );
System.out.println( "Host -> " + in.readLine() );

out.println( "DATA" );
System.out.println( "Host -> " + in.readLine() );

out.println( "SUBJECT: " + "<Betreff>" );
out.println( "<Daten, die wir schicken wollen>" );
out.println( ".");
System.out.println( "Host -> " + in.readLine());

out.println( "QUIT" );

out.close();
in.close();
mailServer.close();
}
catch( IOException e ) { System.err.println( e ); }
}
}

Galileo Computing

17.10 Arbeitsweise eines Web-Servers  downtop

In diesem Kapitel lernen wir die Grundlagen eines Web-Servers kennen. Dazu besprechen wir zunächst das Protokoll HTTP, auf dem das Web basiert. Detaillierte Informationen aus Serversicht werden im Servlets-Kapitel beschrieben. Hier betrachten wir nur die Client-Seite.


Galileo Computing

17.10.1 Das Hypertext Transfer Protocol (HTTP)  downtop

Das Hypertext Transfer Protocol (HTTP) ist ein Protokoll für Hypermedia Systeme und ist im RFC 2616 genau beschrieben. HTTP wird seit dem Aufkommen des World Wide Web – näheres dazu unter http://www.w3.org/ – intensiv genutzt. Das Web basiert auf der Seitenbeschreibungssprache HTML, die 1992 von Tim Berners-Lee entwickelt wurde. Die Entwicklung wurde am CERN vorgenommen und seit den Prototypen ist die Entwicklung nicht nur im Bereich HTML fortgeschritten, sondern auch im Protokoll. Berners-Lee ist allerdings dadurch nicht reich geworden, da er ohne Patent- oder Copyright-Ansprüche nur ein Werkzeug zur Veröffentlichung wissenschaftlicher Berichte ermöglichen wollte.

HTTP definiert eine Kommunikation zwischen Client und einem Server. Typischerweise horcht der Server auf Port 80 (oder 8080) auf Anfragen des Clients. Das Protokoll benutzt eine TCP/IP Socketverbindung und ist deutlich textbasiert. Alle HTTP-Anfragen haben ein allgemeines Format:

gp  Eine Zeile am Anfang
Dies kann entweder eine Anfrage (also eine Nachricht vom Client) oder eine Antwort vom Server sein.
gp  Ein paar Kopf- (engl. Header)zeilen
Informationen über den Client oder Server zum Beispiel über den Inhalt Der Header endet immer mit einer Leerzeile.
gp  Einen Körper (engl. Body)
Der Inhalt der Nachricht. Entweder Benutzerdaten vom Client oder die Antwort vom Server.

Dieses Protokoll ist also sehr einfach und ist auch unabhängig von Datentypen. Dies macht es ideal einsetzbar für verteilte Hypermedia-Informationssysteme.


Galileo Computing

17.10.2 Anfragen an den Server  downtop

Ist die Verbindung aufgebaut, wird eine Anfrage formuliert, auf welches Objekt (Dokument oder Programm) zugegriffen werden soll. Neben der Anfrage wird auch noch das Protokoll festgelegt, mit dem übertragen wird. HTTP 1.0 (und ebenso HTTP 1.1) definiert mehrere Hauptmethoden, wobei drei zu den wichtigsten gehören.

gp  GET
Ist eine Anfrage auf eine Information, die an einer bestimmten Stelle lokalisiert ist. Ein Client kann auch Daten zum Server schicken, indem er sie an die URL anhängt.
gp  POST
Die POST-Methode erlaubt es dem Client, Daten über einen Datenstrom zum Server zu schicken.
gp  HEAD
Funktioniert ähnlich wir GET, nur dass nicht das gesamte Dokument verschickt wird, sondern allein Informationen über das Objekt. So sendet sie zum Beispiel innerhalb einer HTML-Seite die Informationen, die innerhalb von <HEAD>....</HEAD> stehen.

Eine Beispielanfrage an einen Web-Server

Hier ein typisches Beispiel einer GET-Anfrage vom Client an den Standard Web-Server

GET /directory/index.html HTTP/1.0

Das erste Wort ist die Methode des Aufrufs (auch Anfrage, engl. request). Neben den drei oben aufgeführten Methoden GET, POST und HEAD gibt es aber noch weitere, meist finden diese aber nur bei speziellen Anwendungen Verwendung.

Tabelle 17.3   Einige Anfragemethoden von HTTP
Methode Aufgabe
GET Liefert eine Datei
HEAD Liefert nur Dateiinformationen
POST Sendet Daten zum Server
PUT Sendet Dateien zum Server
DELETE Löscht eine Ressource

Der zweite Parameter bei der Anfrage an den Server ist der Dateipfad. Er ist als relative Pfadangabe zu sehen. Er folgt hinter der Methode. Jeder der nachstehenden URLs folgt eine Anfrage:

HTTP://www.trallala.com/
GET / HTTP/1.0
HTTP://www. trallala.com/../index.html
GET /../index.html HTTP/1.0
HTTP://www. trallala.com/classes/applet.html
GET /classes/applet.html HTTP/1.0

Die Anfrage endet nur bei einer Leerzeile (Zeile, die nur ein Carriage-Return (\r) und ein Linefeed (\n) enthält).

Beispiel Wir können das in einer Telnet-Sitzung einfach testen. Geben wir etwa
$ telnet java-tutor.com 80

ein, dann folgt eine lange Ausgabe.


Abbildung 17.1   Get-Anfrage über telnet an einen Webserver
Abbildung

Die Header vom Server werden verdeckt. Sie werden sichtbar, wenn wir etwa einen falschen Dateinnamen mitteilen.

Abbildung 17.2   Ungültige Datei
Abbildung

Nach der Zeile mit der Anfrage können optionale Zeilen gesendet werden. So stellt der Netscape Navigator 2.0 (also der Client) beispielsweise die folgende Anfrage an den Server:

GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/2.0 (Win95; I)
Host: merlin
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*

Hier sehen wir, dass durch diese Plauderei leicht Statistiken vom Browser-Einsatz gemacht werden können. Eine typische Anfrage von Netscape mit der POST-Methode wäre:

POST /cgi/4848 HTTP/1.0
Referer: http://tecfa.unige.ch:7778/4848
Connection: Keep-Alive
User-Agent: Mozilla/3.01 (X11; I; SunOS 5.4 sun4m)
Host: tecfa.unige.ch:7778
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Content-type: application/x-www-form-urlencoded
Content-length: 42
name=Ulli&nachname=Ullenboom

Galileo Computing

17.10.3 Die Antworten vom Server  downtop

Der Server antwortet ebenfalls mit einer Statuszeile, einem Header (mit Informationen über sich selbst) und dem Inhalt. Der Web-Browser muss sich also um eine Antwort vom Web-Server kümmern. Eine vom Microsoft WEB-Server generierte Antwort kann etwa wie folgt aussehen:

HTTP/1.0 200 OK
Server: Microsoft-PWS/2.0
Date: Sat, 09 May 1998 09:52:32 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Sat, 09 May 1998 09:52:22 GMT
Content-Length: 27

Hier kommt die HTML Seite

Die Antwort ist wieder durch eine Leerzeile getrennt. Der Header vom HTTP setzt sich aus drei Teilen zusammen, dem General-Header (dazu gehört etwa Date), Response-Header (dazu gehört Server) und Entity-Header (Content-Length und Content-Type). (Der Client kann zusätzlich noch einen Request Header belegen.)

Jedes Feld im Header besteht aus einem Namen gefolgt von einem Doppelpunkt. Dann folgt der Wert. Die Feldnamen sind unabhängig von der Groß- und Kleinschreibung.

Die erste Zeile wird Statuszeile genannt und beinhaltet die Version des Protokolls und den Statuscode. Der danach folgende Text ist optional und beschreibt in einer menschlichen Form den Statuscode.

Die Version

Die erste Version des Protokolls HTTP (HTTP/0.9) sah nur eine einfache Übertragung von Daten über das Internet vor. HTTP/1.0 war da schon eine Erweiterung, denn die Daten konnten als MIME-Nachrichten verschicket werden. Zudem waren Metadaten (wie die Länge der Botschaft) verfügbar. Da aber HTTP/1.0 Nachteile im Caching besitzt und für jede Datei eine neue Verbindung aufbaut, also keine persistenten Verbindungen unterstützt, wurde das HTTP/1.1 eingeführt.

Der Statuscode

Der Statuscode (engl. status code) gibt Auskunft über das Ergebnis der Anfrage. Er besteht aus einer Zahl mit drei Ziffern. Der zusätzliche optionale Text ist nur für den Menschen.

Das erste Zeichen des Status-Codes definiert die Antwort-Klasse (ähnlich wie es der FTP-Server macht). Die nachfolgenden Ziffern sind keiner Kategorie zuzuordnen. Für das erste Zeichen gibt es fünf Klassen:

gp  1xx: Informierend
Die Anfrage ist angekommen und alles geht weiter.
gp  2xx: Erfolgreich
Die Aktion wurde erfolgreich empfangen, verstanden und akzeptiert.
gp  3xx: Rückfrage
Um die Anfrage auszuführen, sind noch weitere Angaben nötig.
gp  4xx: Fehler beim Client
Die Anfrage ist syntaktisch falsch oder kann nicht ausgeführt werden.
gp  5xx: Fehler beim Server
Der Server kann die wahrscheinlich korrekte Anfrage nicht ausführen.

Gehen wir noch etwas genauer auf die Fehlertypen ein.

Tabelle 17.4   Statuscodes bei Antworten des HTTP-Servers
Status Code Optionaler Text
200 OK
201 Created
202 Accepted
204 No Content
300 Multiple Choices
301 Moved Permanently
302 Moved Temporarily
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable

Am häufigsten handelt es sich bei den Rückgabewerten um

gp  200 OK
Die Anfrage vom Client war korrekt und die Antwort vom Server stellt die gewünschte Information bereit.
gp  404 Not Found
Das referenzierte Dokument kann nicht gefunden werden.
gp  500 Internal Server Error
Meistens durch schlechte CGI-Programme hervorgerufen.

Der Text in der Tabelle kann vom Statuscodes abweichen.

General Header Fields

Zu jeder übermittelten Nachricht (nicht Entity) gibt es abfragbare Felder. Diese gelten sowohl für den Client als auch für den Server. Zu diesen gehören: Cache-Control, Connection, Date, Pragma, Transfer-Encoding, Upgrade, und Via. Zu den Header-Information gehört auch die Uhrzeit des abgesendeten Pakets. Das Datum kann in drei verschiedenen Formaten gesendet werden, wobei das erste zum Internet-Standard gehört, und dementsprechend wünschenswert ist. Es hat gegenüber dem zweiten den Vorteil, dass es eine feste Läge besitzt und das Jahr mit vier Ziffern darstellt:

Sun, 06 Nov 1994 08:49:37 GMT   
; RFC 822, update in RFC 1123
Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsolet seit RFC 1036
Sun Nov 6 08:49:37 1994 ; ANSI C asctime() Format

Ein HTTP/1.1 Client, der die Datum-Werte ausliest, muss also drei Datumsformate akzeptieren.

Felder im Response-Header

Der Response-Header erlaubt dem Server, zusätzliche Informationen, die nicht in der Statuszeile kodiert sind, zu übermitteln. Die Felder geben Auskunft über den Server. Folgende Felder sind möglich: Age, Location, Proxy-Authenticate, Public, Retry-After, Server, Vary, Warning und WWW-Authenticate.

Entity Header Fields

Eine Entity ist eine Information, die aufgrund einer Anfrage gesendet wird. Die Entity besteht aus einer Metainformation (Entity-Header) und der Nachricht selbst (überliefert im Entity-Body). Die Metainformationen, die in einem der Entity-Header-Felder übermittelt werden, sind etwa Informationen über die Länge des Blocks oder über die letzte Änderung der Länge. Ist kein Entity-Body definiert, so liefern die Felder Informationen über die Ressourcen, ohne sie wirklich im Entity-Body zu senden: Allow, Content-Base, Content-Encoding, Content-Language, Content-Length, Content-Location, Content-MD5, Content-Range, Content-Type, ETag, Expires, Last-Modified.

Der Dateiinhalt und der Content-Type

HTTP nutzt die Internet Media-Types im Content-Type. Dieser Content-Type gibt den Datentyp des übermittelten Dateistroms an. Jede über HTTP/1.1 übermittelte Nachricht mit einem Entity-Body sollte einen Header mit Content-Type enthalten. Ist dieser Typ nicht gegeben, so versucht der Client anhand der Endung der URL oder durch Betrachtung des Dateistroms herauszufinden, um was für einen Typ es sich handelt. Bleibt dies jedoch unklar, wird ihm der Typ »application/octet-stream« zugewiesen. Content-Types werden gerne benutzt, um Daten zu komprimieren. Diese verlieren dadurch nicht ihre Identität. In diesem Fall ist das Feld Content-Encoding im Entity-Header gesetzt und bei einem GNU Zip Packverfahren (gzip) ist dann folgende Zeile im Dateistrom mit dabei:

Content-Encoding: gzip

Nach den Headern folgt als Antwort die Datei. Nachdem diese übertragen wurde, wird die Socket-Verbindung geschlossen. Da jedes Anfrage-Antwort-Paar in einer Socket-Verbindung mündet, ist dieses Verfahren nicht besonders schnell und schont auch nicht das Netzwerk, da viele Pakete verschickt werden müssen, die sich um den Aufbau der Leitung kümmern.


Galileo Computing

17.11 Datagram-Sockets  downtop

Neben den Stream-Sockets gibt es im java.net-Paket eine weitere Klasse, die auch den verbindungslosen Paket-Transport erlaubt. Es handelt sich dabei um die Klasse DatagramSocket. Datagram-Sockets basieren auf dem User Datagram Protocoll (UDP). Dies ist auf dem Internet-Protokoll aufgesetzt und erlaubt die ungesicherte Übertragung – ist also auf der Transportschicht des OSI-Modells (Schicht 4) angeordnet. Auch UDP erlaubt es einer Applikation einen Service über einen Port zu kontaktieren. Genauso wie TCP nutzt auch UDP verschiedene Port-Nummern, sodass 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. Jeder Rechner hat 65.536 UDP-Ports genauso wie 65.536 TCP-Ports. So kann ein Server-Socket für TCP am Port 4711 horchen und ein Datagram-Socket auch am Port 4711. 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. Wie werden später ein Programm kennen lernen, welches freie Ports überprüft.

Die Datagram-Sockets brauchen aber im Gegensatz zu den Stream-Sockets keine feste Verbindung zum Server; jedes Datagramm wird einzeln verschickt und kann somit auch auf verschiedenen Wegen und in verschiedener Reihenfolge am Client ankommen. So ist der Begriff »verbindungslos« zu verstehen. Die Datagramme sind von den anderen völlig unabhängig. Ist die Ordnung der Pakete relevant, muss über ein Zeit-Feld 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 brauchen wir uns um den Transport nicht zu kümmern. Wir werden also bei der Benutzung von Stream-Sockets von den unteren Netzwerkschichten getrennt, die die richtige Reihenfolge der Pakete garantiert. Datagram-Sockets nutzten 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/IP 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. Denn 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 Anfange 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- 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 17.5   Welche Klasse wofür?
Klasse Protokoll Verbindungstyp Richtung
Socket TCP Verbindungsorientiert, korrekte Reihenfolge Ausgehend
Server-Socket TCP Verbindungsorientiert, korrekte Reihenfolge Hereinkommend
Datagram-Socket UDP Verbindungslos, Datagramme, beliebige Reihenfolge Ausgehend und Hereinkommend


Galileo Computing

17.11.1 Die Klasse DatagramSocket  downtop

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. Für DatagramSocket-Objekte stehen drei Konstruktoren bereit:

class java.net.DatagramSocket

gp  DatagramSocket() throws SocketException
gp  DatagramSocket( int port ) throws SocketException
gp  DatagramSocket( int port, InetAddress laddr )
throws SocketException

Häufig wird der erste Konstruktor für Client-Programme verwendet und die anderen beiden werden für Server benutzt. Der Unterschied in den Konstruktoren liegt darin, an welchen 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 Clientseite festzusetzen ist nur dann wichtig, wenn hinter einer Firewall operiert wird.

Abbildung


Galileo Computing

17.11.2 Datagramme und die Klasse DatagramPacket  downtop

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, wo 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 hört:

byte data[] = new Bytes[1024];

DatagramSocket socket = new DatagramSocket( 4711 );

DatagramPacket packet = new DatagramPacket( data, data.length );

socket.receive( packet );
Abbildung


Galileo Computing

17.11.3 Auf ein hereinkommendes Paket warten  downtop

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 der Methode 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 17.11   UDPServer.java
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 );
}
}
}

Galileo Computing

17.11.4 Ein Paket zum Senden vorbereiten  downtop

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 sendet 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 );

Galileo Computing

17.11.5 Methoden der Klasse DatagramPacket  downtop

Das DatagramPaket ist auch nachträglich veränderbar, und kann mit Methoden angepasst und auch ausgelesen werden:

class java.net.DatagramSocket

gp  InetAddress getAddress()
Hier müssen wir unterscheiden, ob das Paket einkommend oder ausgehend ist. Für ein einkommendes DatagramPacket liefert die Methode die Adresse, von der das Paket kam. Für ein ausgehendes Paket liefert getAddress() die Adresse, an wen das Paket geht.
gp  public int getPort()
Für ein einkommendes Paket liefert es die Port-Nummer vom Sender. Für ein ausgehendes Paket liefert getPort() den Port wohin das Datagram geht.

Das folgende Programm zeigt ein zu sendendes Paket und wir können die abgelegten Informationen wieder auslesen.

Listing 17.12   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()) );
}
}

Galileo Computing

17.11.6 Das Paket senden  downtop

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 zur Vorbereitung auf einen eigenen UDP-Zeitserver.

Listing 17.13   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 );
}
}
}

Galileo Computing

17.11.7 Die Zeitdienste und ein eigener Server und Client  downtop

Ein Zeitserver bietet Clients immer die aktuelle Server-Uhrzeit an. Wir halten uns hier am Daytime Protocol, welches im RFC 867 beschrieben ist. Ein Server lauscht auf Port 13 nach UDP-Paketen 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 sowieso 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 Daten 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.

Client

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 einkommende Paket gewartet. Wenn dieses kommt, dann liefert getData() eine Zeichenkette mit der Zeit.

Listing 17.14   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 );
}
}
}

Zeitserver

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 17.15   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 );
}
}
}

Galileo Computing

17.12 Internet Control Message Protocol (ICMPdowntop

Neben dem Internet Protokoll werden noch mehrere Steuerprotokolle auf der Vermittlungsschicht eingesetzt. Darunter befindet sich auch das Internet Control Message Protocol (ICMP), das im Prinzip ein IP-Paket mit bestimmten Flags ist. Dieses ist im RFC 792 definiert. Da das Funktionieren des Internets sehr stark von Routern abhängt, wurde ICMP entwickelt, um unerwartete Ereignisse und Zusatzinformationen zu melden. Java unterstützt zurzeit nur Sockets vom Typ SOCK_STREAM (TCP) und SOCK_DGRAM (UDP), aber keine IP-Pakete (und damit auch das Internet Control Message Protocol) mit dem Sock-Typ SOCK_RAW. Daher können in Java mit den Standardbibliotheken keine IP-Pakete und ICMP-Nachrichten verschickt werden. Unwahrscheinlich ist auch, dass dies in den nächsten Javaversionen nachgereicht wird, da dadurch neue Sicherheitsprobleme entstehen. Viele Unix-Systeme erlauben Sockets vom Typ SOCK_RAW nur unter Root-Rechten.


Galileo Computing

17.12.1 Ping  downtop

Da Dienste wie »ping« oder »traceroute« auf ICMP aufbauen, ist eine Implementierung dieser Tools in Java momentan nicht möglich und so muss auf JNI zurückgegriffen werden. Eine Lösung unter Windows kommt von Isabel García und Óscar Fernández, die unter www.geocities.com/SiliconValley/Bit/5716/ping.html eine Bibliothek (ohne Quellcode) zur Verfügung stellen.

Ein Ping-Programm besteht im Wesentlichen aus der Initialisierung der Klasse, dem Absenden eines Pings und dem Empfang eines Pongs. Das empfangene Datenpaket liegt dabei in einem Stringfeld aus vier Werten, in denen dann Zeit und IP-Adressen des Partners liegen:

String dataRcv[] = new String[4];

PingICMP ping = new PingICMP();

ping.begin();

if ( ping.ping("127.0.0.1", 5, 1) )
{
ping.pong( dataRcv );

System.out.println( "Host: " + dataRcv[0] +
" ping time: " + dataRcv[3] );
}
final class estadisticas.icmp.PingICMP

gp  PingICMP ()
Erzeugt neues PingICMP-Objekt.
gp  final boolean begin ()
Initialisiert den Socket zum Senden und Empfangen. Nur genau ein Socket darf offen sein. Der Rückgabewert ist false, falls der Socket nicht initialisiert werden konnte.
gp  final boolean end ()
Schließt den Socket, sodass später noch einmal mit begin() eine Sitzung eröffnet werden kann. Der Rückgabewert ist false, falls der Socket nicht geschlossen werden konnte.
gp  final boolean ping( String address, int identifier,int seqNumber )
Sendet ein ping an die Empfänger IP-Adresse. Der Identifizierer ist wie die Sequenznummer eine Zahl zwischen 0 und 65.535. Der Rückgabewert ist false, falls das Paket nicht versendet werden konnte oder ein Parameter falsch ist.
gp  final boolean pong( String results[] )
Die Funktion wartet so lange blockierend, bis ein Echo eintrifft. Das bestehende Feld wird mit vier Werten gefüllt. Der erste Wert ist die IP-Adresse der entfernten Maschine, dann folgt die Identifikation, die Sequenznummer und an Position drei die Pingzeit. Die Rückgabe ist false, falls ein Fehler auftritt oder das Feld nicht mindestens vier Elemente fasst. ping()/pong() sollte mehrmals aufgerufen werden, damit die Zeiten repräsentativ sind.

Galileo Computing

17.13 Multicast-Kommunikation  toptop

Besteht die Notwendigkeit, dass ein Server Datagramme gleichzeitig an mehrere Clients schickt, dann müssen wir hier auch mehrere einzelne Verbindungen (sogenannte Unicast Verbindungen) aufbauen. Diese Möglichkeit ist jedoch sehr ineffizient und belastet die Bandbreite. Anwendungsfelder für Multicast-Kommunikation sind etwa Video oder Audio, Chat-Sitzungen oder interaktive Spiele.

In Java lässt sich für diese Aufgabe das Multicasting einsetzen, wenn das Betriebssystem und die Router Multicasting-fähig sind. Multicasting-fähige Router werden auch MRouter genannt. Damit der Server sendet und mehrere Clients empfangen können, melden sich die Clients bei einer gewünschten Multicast-Gruppe an. Eine Multicast-Gruppe besteht aus mehreren Rechnern, die sich eine Multicast-Adresse teilen. Der Server sendet dann die Datagramme über einen Multicast-Socket und wird von allen in der Liste empfangen. Die weitere Verarbeitung übernehmen das Betriebssystem und die Router. Für eine Multicast-Gruppe gibt es spezielle Multicast-Adressen. Diese bewegen sich im Bereich von 224.0.0.0 bis 239.255.255.255. Statische Multicast-Adressen werden – wie andere IP-Adressen auch – von der IANA vergeben. Für eigene Testzwecke bieten sich Adressen im Bereich 224.0.1.27 und 224.0.1.225 an.






1    Obwohl Al Gore in einem Interview für CNN gegenüber dem Journalisten Wolf Blitzer selbstsicher erwähnte, die Entwicklung des Internets wäre seine Erfindung, müssen wir das als süffisantes Politiker-Geschwätz abtun. (Al Gore war zu diesem Zeitpunkt gerade einmal einundzwanzig Jahre alt und wurde erst acht Jahre später in das US-Repräsentantenhaus gewählt.) Leider kannte Blitzer die wahre Geschichte nicht, aber die Medienkonsumenten haben kräftig gelacht.

2    Wer sich mit der Implementierung von Protokoll-Handlern näher auseinandersetzen möchte, der findet unter http://java.sun.com/people/brown/ eine Implementierung vom Finger Protokoll-Handler.

3    Nicht zu verwechseln mit »Illinois Association of Nurse Anesthetists« beziehungsweise »Intermodal Association of North America«!

4    Die einzelnen Programme liegen allerdings auch unter http://www.sockets.com/sample.htm.

5    Eine Lempel-Ziv Kodierung (LZ77) mit einem 32 –Bit-CRC. Beschrieben in RFC 1952.

6    Den Zeitdienst gibt es auch als TCP-Dienst, der hier aber keine Rolle spielt.

  

Java 2




Copyright © Galileo Press GmbH 2002
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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de