Weitere aktuelle Java-Titel finden Sie bei dpunkt.
 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index


13.2.3

Datagramm-Sockets


Die Übertragung mit Datagramm-Sockets benötigt keine feste Verbindung zum Zielrechner. Sie beruht auf dem Verschicken von Datagrammen. In einem Datagramm ist die Adresse des Ziel- und des Ausgangsrechners vermerkt. Da jedes Paket bei Datagramm-Sockets einzeln losgeschickt wird, kann es vorkommen, dass die Pakete in einer anderen Reihenfolge am Zielrechner ankommen als sie abgeschickt wurden. Jedes abgeschickte Datagramm könnte theoretisch einen anderen Weg zum Zielrechner nehmen.

Im Gegensatz zu Streamsockets, die auf einen bestimmten Host fixiert sind und nur mit diesem Daten austauschen können, sind Datagramm-Sockets flexibler. Über einen Datagramm-Socket kann mit verschiedenen Rechnern kommuniziert werden. Der jeweils zu kontaktierende Host wird nicht bei der Initialisierung des Sockets festgelegt, sondern im abgeschickten Datagramm vermerkt. Ein Datagramm-Socket wird in Java durch die Klasse DatagramSocket repräsentiert.

Hierzu ein Beispiel: Das Beispiel-Applet besitzt eine TextArea und einen Button. Wird der Button gedrückt, so erscheint in der TextArea das aktuelle Datum und die Tageszeit. Das Datum soll allerdings nicht mit Hilfe der Klasse java.util.Date ermittelt, sondern vom Daytime-Server zugeschickt werden.

Um vom daytime-Server ein Datum zu erhalten, muss man ihm ein Datagramm zusenden. Kommt bei ihm ein Datagramm an, schickt er (unabhängig vom Inhalt des ankommenden Pakets) ein Datagramm mit der aktuellen Tages- und Uhrzeit an den Absender zurück.

Folgender Auszug aus der init()-Methode des Beispiel-Applets initialisiert den Socket:
  try {
    // Datagrammsocket erzeugen
    socket = new DatagramSocket();
    try {
      // Adresse des Applet-Hosts speichern
      address =
        InetAddress.getByName(getCodeBase().getHost());
    }
    catch (UnknownHostException e) {
      System.err.println("Error: "+e);
      datebutton.setEnabled(false);
      socket.close();
    }
  }
  catch (SocketException e) {
    System.err.println("Error: "+e);
    datebutton.setEnabled(false);
  }
Zuerst wird ein Exemplar von DatagramSocket erzeugt:
  socket = new DatagramSocket();
Der Konstruktor bekommt in diesem Fall keine Parameter übergeben. Im Unterschied zu Stream-Sockets, die scheinbar direkt mit einem anderen Rechner verbunden sind, ist ein Datagramm-Socket nur an einen Port am lokalen Rechner gebunden.

Die Port-Nummer, an die der Socket gebunden ist, wird auf diese Weise aus der Liste der freien Port-Nummern beliebig gewählt. Diese Art der Initialisierung wird vor allem bei der Programmierung von Clients gewählt.

Es ist jedoch auch möglich, dem Konstruktor die Port-Nummer explizit zu übergeben, z. B.:
  socket = new DatagramSocket(33333);
Hierbei muss jedoch darauf geachtet werden, dass der gewählte Port nicht schon von einem anderen Dienst belegt ist. Ist dies der Fall, wird eine SocketException ausgelöst. Diese Technik wird hauptsächlich bei der Server-Programmierung auf Datagrammbasis verwendet, da ein Server üblicherweise unter einer bestimmten Port-Nummer zu finden ist. Das ist nur gewährleistet, wenn die Port-Nummer explizit bei der Initialisierung übergeben wird.

Zum Verschicken eines Datagramms braucht man ein Exemplar der Klasse InetAddress. Dieses beinhaltet die Adresse des Server-Hosts. Allgemein kann man die einem Host-Namen zugehörige InetAddress durch Aufruf der statischen Methode getByName(String) der Klasse InetAddress ermitteln.

Im obigen Beispiel wird folgendermaßen vorgegangen:
  address =
    InetAddress.getByName(getCodeBase().getHost());
Zuerst wird der Host-Name mit getCodeBase().getHost() ermittelt und anschließend an getByName() übergeben. Dadurch erhält man die InetAddress des Applet-Hosts.

Tritt bei einer dieser Aktionen ein Fehler auf, wird der Button dateButton, durch dessen Druck später das Datum angefordert werden kann, »nicht selektierbar« gestaltet und ein entsprechender Fehlertext auf der Standardfehlerausgabe ausgegeben. Dies ist sinnvoll, da dann sowieso keine Kommunikation mit dem daytime-Server möglich ist.

Hiermit wäre das Problem der Kommunikation abgedeckt. Das Kernstück des Applets jedoch bildet die Methode getDate(). Sie wird jedesmal aufgerufen, wenn das Datum neu angefordert wird:
  public void getDate() {
    // Erzeugen eines neuen Datagrammes
    packet = new DatagramPacket(buffer, 256, address, 13);
    String date;  // String, der das Datum aufnimmt
    try {
      // Verschicken eines des leeren Datagrammes
      socket.send(packet);
      // Warten auf Ankunft eines Datagrammes
      socket.receive(packet);
      // Daten aus Datagramm extrahieren
      date = new String(packet.getData());
    }
    catch(IOException e) {
      date = "cannot get date";
    }
    incoming.append(date);  // Anzeigen des Datums
  }

Die Kommunikation über Datagramm-Sockets erfolgt mit so genannten Datagrammpaketen. Java stellt hierfür die Klasse DatagramPacket zur Verfügung. Eine Kommunikation ist erst möglich, wenn ein Exemplar dieser Klasse vorhanden ist:
 
  packet = new DatagramPacket(buffer, 256, address, 13);
Dem Konstruktor werden folgende Parameter übergeben: Der daytime-Server ist bei UNIX-Rechnern üblicherweise unter Port-Nummer 13 zu finden.

address wurde bereits in init() erzeugt. Der Byte-Puffer dient dazu, die zu verschickenden Daten aufzunehmen. Damit hat man alle zum Erreichen des Zielrechners erforderlichen Informationen angegeben.

Seit[1.2] dem JDK 1.2 verfügt die Klasse DatagramSocket über die Methoden connect() und disconnect(). connect()besitzt zwei Parameter: Ein Exemplar von InetAddress und eine Port-Nummer. Nach dem Aufruf von connect() können nur noch Datagramme an die angegebene Adresse verschickt und empfangen werden. Nach dem Aufruf von disconnect() können Pakete wieder an beliebige Adressen verschickt bzw. von beliebigen Adressen empfangen werden.

In diesem Beispiel ist es nicht notwendig, Daten zu verschicken. Der daytime-Server reagiert auf jedes ankommende Paket, unabhängig vom Inhalt, gleich. Deshalb genügt es, ein leeres Datagramm abzusenden.

Das Datagramm kann mit der Methode send() verschickt werden:
  socket.send(packet);
Ist das Paket einmal abgeschickt, wartet das Applet auf die Antwort des Servers:
  socket.receive(packet);
receive() erwartet ein Exemplar der Klasse DatagramPacket als Parameter. Die Programmausführung wird so lange blockiert, bis ein Paket eingetroffen ist. Im Beispiel wird hierfür dasselbe DatagramPacket-Objekt verwendet wie beim Verschicken.

Wenn ein Applet z. B. Datagramme nur erhält, aber nicht verschickt, ist es nicht nötig, Port-Nummer und InetAddress eines Rechners anzugeben. Zu diesem Zweck besitzt DatagramPacket einen zweiten Konstruktor:
  packet = new DatagramPacket(buffer, 256);
Ihm wird nur der Byte-Puffer und die Größe dieses Puffers übergegeben.

Ein zweites Exemplar von DatagramPacket zu erzeugen ist im obigen Beispiel nicht erforderlich, da ein DatagramPacket mit Host-Name und Port-Nummer auch zum Empfangen benutzt werden kann. Ein DatagramPacket ohne Host-Name und Port-Nummer dagegen kann nicht verschickt werden. Auch send() und receive() können eine IOException verursachen. Es sollte beachtet werden, dass bei einem Aufruf von receive() nur so viele Daten aufgenommen werden können, bis die Kapazität des DatagramPacket-Objekts erschöpft ist. Wird der Versuch unternommen, Pakete zu empfangen, deren Inhalt größer als die Kapazität des DatagramPacket-Objektes ist, so werden alle Daten nach Überschreitung der Kapazitätsgrenze verworfen. Die Paketgrößen zwischen Sender und Empfänger sollten also übereinstimmen.

Ist ein Paket angekommen, kann man mit verschiedenen Methoden auf dessen Daten zugreifen: Man benötigt in diesem Beispiel nur die Methode getData():
  date = new String(packet.getData());
Das auf diese Weise erhaltene Array von Bytes wird in einen String umgewandelt und anschließend in der TextArea ausgegeben.

Wie auch Streamsockets sollten Datagramm-Sockets immer wieder geschlossen werden. Dies veranlasst die Methode destroy():
  public void destroy() {
    socket.close();   // Schließen des Sockets
  }

Der Socket wird bei Beendigung des Applets geschlossen. Bereits bei der Initialisierung wurde close() verwendet, ohne dass das Auftauchen dieser Methode näher erläutert wurde. Das Schließen des Sockets ist dort nur erforderlich, wenn InetAddress.getByName() keinen gültigen Wert liefert, weil ein falscher Host-Name angegeben wird.

Abbildung 13.3: Eine Verbindung zum daytime-Server
Abbildung 13.3


Material zum Beispiel

Mit der Klasse DatagramSocket ist es auch möglich, Multicast-Pakete zu versenden. Hierzu muss man einfach als Zieladresse eine Multicast-Adresse angeben. Zum Empfangen von Multicastpaketen muss die Klasse MulticastSocket benutzt werden. Dort sind Methoden definiert, die es erlauben, sich Multicast-Gruppen anzuschließen bzw. zu verlassen. Auf die weitere Benutzung der Klasse MulticastSocket wird an dieser Stelle jedoch nicht weiter eingegangen, da dies den Rahmen des Buches sprengen würde.


 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index

Copyright © 2002 dpunkt.Verlag, Heidelberg. Alle Rechte vorbehalten.