Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 16 Das Netz
  gp 16.1 Grundlegende Begriffe
    gp 16.1.1 Internet-Standards und RFC
  gp 16.2 URI und URL
    gp 16.2.1 URI
    gp 16.2.2 Die Klasse URL
    gp 16.2.3 Informationen über eine URL
    gp 16.2.4 Der Zugriff auf die Daten über die Klasse URL
    gp 16.2.5 Verbindungen durch einen Proxy-Server
  gp 16.3 Die Klasse URLConnection
    gp 16.3.1 Methoden und Anwendung von URLConnection
    gp 16.3.2 Protokoll- und Content-Handler
    gp 16.3.3 Bilder-Handler
    gp 16.3.4 Zusammenfassung Content- und Protokoll-Handler
    gp 16.3.5 Im Detail: vom URL zu URLConnection
    gp 16.3.6 Der Protokoll-Handler für Jar-Dateien
    gp 16.3.7 Autorisierte URL-Verbindungen und Proxy-Authentifizierung mit Basic Authentication
  gp 16.4 Das Common Gateway Interface
    gp 16.4.1 Parameter für ein CGI-Programm
    gp 16.4.2 Kodieren der Parameter für CGI-Programme
    gp 16.4.3 Eine Suchmaschine ansprechen
  gp 16.5 Host- und IP-Adressen
    gp 16.5.1 Lebt der Rechner?
    gp 16.5.2 Das Netz ist Klasse …
    gp 16.5.3 IP-Adresse des lokalen Hosts
  gp 16.6 NetworkInterface
  gp 16.7 Mit dem Socket zum Server
    gp 16.7.1 Das Netzwerk ist der Computer
    gp 16.7.2 Sockets
    gp 16.7.3 Standarddienste unter Windows nachinstallieren
    gp 16.7.4 Eine Verbindung zum Server aufbauen
    gp 16.7.5 Server unter Spannung: die Ströme
    gp 16.7.6 Die Verbindung wieder abbauen
    gp 16.7.7 Ein kleines Echo – lebt der Rechner noch?
    gp 16.7.8 Blockierendes Lesen
    gp 16.7.9 Informationen über den Socket
    gp 16.7.10 Mit telnet an den Ports horchen
    gp 16.7.11 Reine Verbindungsdaten über SocketAddress
  gp 16.8 Client/Server-Kommunikation
    gp 16.8.1 Warten auf Verbindungen
    gp 16.8.2 Ein Multiplikations-Server
    gp 16.8.3 Von außen erreichbar sein
  gp 16.9 SSL-Verbindungen mit JSSE
  gp 16.10 Apache Jakarta Commons HttpClient und Net
    gp 16.10.1 Jakarta Commons HttpClient
    gp 16.10.2 Jakarta Commons Net
  gp 16.11 E-Mail
    gp 16.11.1 Wie eine E-Mail um die Welt geht
    gp 16.11.2 Das Simple Mail Transfer Protocol und RFC 822
    gp 16.11.3 POP (Post Office Protocol)
    gp 16.11.4 E-Mails versenden mit der JavaMail API von SUN
    gp 16.11.5 MimeMultipart-Nachrichten schicken
    gp 16.11.6 E-Mails mittels POP3 abrufen
    gp 16.11.7 Ereignisse und Suchen
  gp 16.12 Arbeitsweise eines Web-Servers
    gp 16.12.1 Das Hypertext Transfer Protocol (HTTP)
    gp 16.12.2 Anfragen an den Server
    gp 16.12.3 Die Antworten vom Server
  gp 16.13 Datagram-Sockets
    gp 16.13.1 Die Klasse DatagramSocket
    gp 16.13.2 Datagramme und die Klasse DatagramPacket
    gp 16.13.3 Auf ein hereinkommendes Paket warten
    gp 16.13.4 Ein Paket zum Senden vorbereiten
    gp 16.13.5 Methoden der Klasse DatagramPacket
    gp 16.13.6 Das Paket senden
  gp 16.14 Tiefer liegende Netzwerkeigenschaften
    gp 16.14.1 Internet Control Message Protocol (ICMP)
    gp 16.14.2 MAC-Adresse
  gp 16.15 Multicast-Kommunikation


Galileo Computing

16.8 Client/Server-Kommunikatiodowntop

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. Der Konstruktor bekommt einfach die Port-Nummer, zu der sich Clients verbinden können, als Argument übergeben.


Beispiel   Wir richten einen Server ein, der am Port 1234 horcht.
ServerSocket serverSocket = new ServerSocket( 1234 );

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 zu keinen Problemen führen, da er auf dem eigenen Rechner gewöhnlich nicht installiert ist. Damit sich der Java-Server nicht mit einem anderen Server kneift, sollten wir einen Blick auf die aktuell laufenden Dienste werfen. Unter Windows listet auf der Kommandozeile netstat -an die laufenden Serverdienste und die belegten Ports auf. Bei Unix-Systemen können nur Root-Besitzer Ports unter 1024 nutzen. Unter dem herkömmlichen Windows ist das egal. Läuft ein Server unendlich, so muss darauf geachtet werden, eine alte Instanz erst zu beenden, damit er neu gestartet werden kann.

Abbildung
Hier klicken, um das Bild zu Vergrößern


Galileo Computing

16.8.1 Warten auf Verbindungen  downtop

Nachdem der Socket eingerichtet ist, kann er auf hereinkommende Meldungen reagieren. Mit der blockierenden 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 Ausgabekanäle und kommunizieren. In der Regel wird ein Thread den Client-Socket annehmen, damit der Server schnell wieder verfügbar ist und neue Verbindungen annehmen und verarbeiten kann.

Wichtig bleibt zu bemerken, dass die Konversation nicht über den Server-Socket selbst läuft. Dieser ist immer noch aktiv und horchIt 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. Einen Server-Socket erzeugen, der horcht.
       
2. Mit der accept()-Methode auf neue Verbindungen warten.
       
3. Ein- und Ausgabestrom vom zurückgegebenen Socket erzeugen.
       
4. Mit einem definierten Protokoll die Konversation unterhalten.
       
5. Stream von Client und Socket schließen.
       
6. IBei Schritt 2 weitermachen oder Server-Socket schließen.
       

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.


Beispiel   Der Server soll höchstens eine Minute auf eingehende Verbindungen warten.
ServerSocket server = new ServerSocket( port );
// Timeout nach 1 Minute
server.setSoTimeout( 60000 );
try {
  Socket socket = server.accept();
} catch ( InterruptedIOException e ) {
  System.err.println( "Timeout after one minute" );
}


Galileo Computing

16.8.2 Ein Multiplikations-Server  downtop

Der erste Server, den wir programmieren wollen, soll zwei Zahlen multiplizieren. Der Konstruktor nimmt einen Port, mit dem der ServerSocket aufgebaut wird. Anschließend wartet startServing() auf eingehende Verbindungen. Steht die Verbindung, definiert handleConnection() das Protokoll: im Eingabestrom befinden sich zwei Zahlen, die multipliziert zurückzuschreiben sind.

Listing 16.13   com/javatutor/insel/net/MulServer.java

package com.javatutor.insel.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MulServer
{
  private final ServerSocket server;
  public MulServer( int port ) throws IOException
  {
    server = new ServerSocket( port );
  }
  private void startServing()
  {
    while ( true )
    {
      Socket client = null;
      try
      {
        client = server.accept();
        handleConnection ( client );
      }
      catch ( IOException e ) {
        e.printStackTrace();
      }
      finally {
        if ( client != null )
          try { client.close(); } catch ( IOException e ) { e.printStackTrace(); }
      }
    }
  }
  private void handleConnection( Socket client ) throws IOException
  {
    InputStream  in  = client.getInputStream();
    OutputStream out = client.getOutputStream();
    int factor1 = in.read();
    int factor2 = in.read();
    out.write( factor1 * factor2 );
  }
  public static void main( String[] args ) throws IOException
  {
    MulServer server = new MulServer( 3141 );
    server.startServing();
  }
}

Kommt es zu einem Verbindungsaufbau, erfragt der Server den Client und die Kommunikationsströme. Über den Eingabestrom werden zwei byte erwartet; die blockierende read()-Methode übernimmt diese Aufgabe. Kommen die Byte nicht an, wartet der Server ewig auf seine Daten und ist unterdessen blockiert, da er in diese Implementierung nur einen Client bedient. Bekommt er die beiden Byte, multipliziert er sie und sendet das Ergebnis zurück. Nach dem Senden ist das Protokoll beendet, und die Verbindung zum Client kann unterbrochen werden. Durch die Endlosschleife ist der Server bereit für neue Anfragen.

Auf der anderen Seite ist der Client, der aktiv eine Verbindung zum Server aufbaut. Er nutzt ein mit Internet-Adresse und Port initialisiertes Socket-Objekt, um ein- und ausgehenden Datenstrom zu erfragen und zwei Byte zu übertragen. Das Ergebnis sammelt er ein und gibt es auf dem Bildschirm aus. Nach der Kommunikation wird die Verbindung geschlossen, um die nötigen Ressourcen wieder freizugeben.

Listing 16.14   com/javatutor/insel/net/MulClient.java

package com.javatutor.insel.net;
import java.net.*;
import java.io.*;
class MulClient
{
  public static void main( String[] args )
  {
    Socket server = null;
    try
    {
      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 );
    }
    catch ( UnknownHostException e ) {
      e.printStackTrace();
    }
    catch ( IOException e ) {
      e.printStackTrace();
    }
    finally
    {
      if ( server != null )
        try { server.close(); } catch ( IOException e ) { e.printStackTrace(); }
    }
  }
}

Erweiterung der Ströme

Natürlich ist der Server in der Funktionalität beschränkt, da nur Byte übertragen werden. So kann das Ergebnis nicht größer als 127 werden, da es sonst falsch übermittelt würde. Dennoch lässt sich das Programm leicht als Ausgangspunkt für einige Server erweitern. Erster Schritt wäre zum Beispiel die Aufwertung des einfachen InputStream/OutputStream zum DataInputStream/DataOutputStream.

In einer realistischen Client/Server-Anwendung mit Sockets würden immer gepufferte Ströme eingesetzt, um nicht laufend kleine Datenpakete zu senden. Werden jedoch Ströme wie BufferedInputStream oder BufferedOutputStream eingesetzt, sollten wir uns bewusst sein, dass die Informationen im Puffer zwischengespeichert und insofern nicht direkt zum anderen Rechner übertragen werden. In einem Frage-Antwort-Szenario ist die Anfrage direkt zu übertragen werden und darf nicht im Puffer verweilen. Zu passenden Zeitpunkten müssen somit mit der flush()-Methode der Puffer-Klasse die aufgenommenen Daten übertragen werden, damit die Kommunikation weitergeht

Erweiterung durch Multi-Threading

Ein anderer Punkt ist die Tatsache, dass Server im Allgemeinen multithreaded ausgelegt sind, damit sie mehrere Anfragen gleichzeitig ausführen können. Die Threads werden nicht pro Anfrager erzeugt, da dies zu teuer ist, sondern einem Thread-Pool entnommen. Mit Java 5 gibt es hierfür die Thread-Pool-Klasse, die diese Arbeit vorzüglich bewältigt.


Galileo Computing

16.8.3 Von außen erreichbar sein  toptop

Ein Server kann nur auf unserem Rechner gestartet werden. Ist der Rechner vom Internet aus erreichbar, können externe auf ihn zugreifen. Anders sieht es aus, wenn der Rechner eine Internet-Adresse hat, die von außen nicht sichtbar ist, weil er zum Beispiel über einen Router ins Internet geht. Dann vergibt dieser Router eine eigene Adresse – die oft mit 192.168 oder 10 beginnt – und setzt sie per NAT um, sodass unsere private Adresse außen verborgen bleibt. Die Frage ist nun, ob wir trotzdem einen Serverdienst anbieten können.

Diese Möglichkeit gibt es tatsächlich, wenn einige Randbedingungen gegeben sind: Zunächst muss unsere interne IP-Adresse relativ stabil sein, und unsere äußere IP-Adresse vom Router ins Internet ebenso. Dann muss auf dem Router eine Einstellung vorgenommen werden, damit wir auf bestimmten Ports von außen angesprochen werden können. Diese Einstellung sieht bei jedem Router anders aus und in größeren Unternehmen wird der Sicherheitsverantwortliche dies nicht akzeptieren. Nach der entsprechenden Einstellung benötigen wir eine globale Adresse, die wir weitergeben können. Dies wird keine IP-Adresse sein, sondern ein Name, der über DNS aufgelöst wird. Das war dann schon der Trick, weil der konstant bleibende Name mit immer unterschiedlichen IP-Adressen verbunden werden kann, was die Tatsache abbildet, dass wir zum Beispiel mit einem Einwahl-Router immer unterschiedliche IP-Adressen bekommen. Daher heißt diese Technik auch dynamisches DNS. Eine feste URL gibt es bei unterschiedlichen Anbietern oft auch unentgeltlich, zum Beispiel bei http://www.orgdns.org/. Nach dieser Anmeldung lässt sich ein Subname registrieren, sodass etwa unter meinserver.orgdns.org die IP-Adresse des Einwahl-Routers steht. Dieser leitet nach der entsprechenden Einstellung eine Anfrage an unseren Rechner mit unserem Java-Server weiter.

 << zurück




Copyright © Galileo Press GmbH 2005
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, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de