Zurück zum Inhaltsverzeichnis des Manuskripts verteilte Systeme

2.2.3 Java Datagram Sockets

Datagrammdienst

Neben dem Datenstromdienst (Stream Service) mit TCP gibt es im Internet für Anwendungsprogramme einen zweiten Transportdienst, denn das Transportprotokoll UDP stellt einen Datagrammdienst (Datagram Service) zur Verfügung. Eine Anwendung, die als Sender UDP verwenden will, muss ihre Daten in Datagramme verpacken und diese dann der UDP-Software übergeben. Jedes Datagramm wird unabhängig von allen anderen Datagrammen und möglicherweise auf unterschiedlichen Wegen zum Empfänger transportiert. Dieser muss die Datagamme dann auspacken, um an die ursprünglichen Daten zu gelangen.

Im Gegensatz zu dem Stream Service TCP folgt der Datagrammdienst UDP einem Peer-to-Peer-Kommunikationsmodell. Dabei sind die beiden Kommunikationspartner gleichberechtigt, das Modell ist symmetrisch. Eine bestimmte Reihenfolge bei der Verbindungsaufnahme ist nicht vorgesehen, sondern Sache der Anwendungsprogrammierung.

Verbindungsorientierung und Verbindungslosigkeit

An dieser Stelle soll noch auf zwei Begriffe hingewiesen werden, die in der Literatur häufig verwendet werden, und die die Art der beiden Internet-Transportdienste charakterisieren: Datenstromdienste werden als verbindungsorientiert bezeichnet, während man Datagrammdienste verbindungslos nennt.

Datagram Sockets

Wollen zwei Anwendungen mit dem Datagrammdienst UDP miteinander kommunizieren, dann richtet jede von ihnen einen sogenannten Datagrammsocket (Datagram Socket) ein. Jede der beiden Anwendungen kann Daten, die sie zum Partner senden will, in ein Datagramm packen und ihrem Socket zum Transport übergeben. Versucht eine Anwendung, ein Datagramm einem Datagrammsocket zu entnehmen, ohne dass eines vorhanden ist, dann wird sie so lange blockiert, bis ihr Entnahmeversuch erfolgreich ist.

Datagram Packets

Der Internet-Transportdienst für Datagramme hat eine Größenbeschränkung. Datagramme im Internet dürfen einschließlich der Verwaltungsinformation höchstens 65535 Bytes groß sein. Der Aufbau von Datagrammen wird im Abschnitt 4.3 (User Datagram Protocol) näher beschrieben. Anwendungen, die Datagramme erstellen und versenden wollen, müssen diese Grenze beachten. Der Versuch, in einem Java-Programm ein zu großes Datagramm zu versenden, führt zu einem Laufzeitfehler.

Java-Klassen

Um unter Java Datagram Sockets einzurichten, benutzen beide Kommunikationspartner die beiden folgenden Klassen aus dem java.net-Package:

Die Klasse DatagramPacket wird benötigt, um Daten in Datagramme ein- bzw. sie aus Datagrammen wieder auszupacken. Und mit Hilfe der Klasse DatagramSocket werden Datagrammsockets eingerichtet. Im Folgenden wird anhand eines kleinen Beispiels das Vorgehen der Anwendungen beim Empfangen und Senden von Datagrammen beschrieben. Man beachte dabei, dass Sender und Empfänger von Datagrammen gleichberechtigt sind und jederzeit ihre Rollen wechseln können.

Empfängeraktionen

Zunächst sollen die grundlegenden Aktionen eines Empfängers eines Datagramms gezeigt werden. Eine Anwendung, die ein Datagramm erwartet, benötigt ein leeres Datagramm, so eine Art Datagrammhülle, mit ausreichend viel Platz für die zu erwartenden Daten. Wählt sie diesen Platz zu klein, dann erhält sie lediglich den ersten Teil der ankommenden Daten. Der Rest wird ohne jede Meldung einfach abgeschnitten.

Für die Aufnahme der ankommenden Daten ist ein Byte-Array zu verwenden. Man beachte, dass es ganz allein Sache der Anwendung ist, dieses Array groß genug zu machen. In dem folgenden Beispiel werden von der Anwendung 512 Bytes als ausreichend angesehen:

byte[] b = new byte[512];

Dieses Array wird mit Hilfe eines DatagramPacket-Konstruktors Bestandteil eines vorläufig noch leeren Datagramms namens dp:

DatagramPacket dp = new DatagramPacket(b, b.length);

Der zweite Parameter des Konstruktors gibt an, wie viele Bytes höchstens aufgenommen werden sollen. Die Angabe kann kleiner als die Länge des Arrays sein. Nachdem ein leeres Datagramm mit hoffentlich ausreichend viel Platz vorhanden ist, richtet die Anwendung einen Datagram Socket an einem bestimmten Port ein. An diesem Port soll sie dann auf die Ankunft eines Datagramms warten. Beispielsweise wird mit

DatagramSocket ds = new DatagramSocket(8641);

ein Datagrammsocket ds mit dem Port 8641 eingerichtet. Zu diesem Socket ist nach der Abarbeitung des Konstruktors die zugehörige Socketinformation noch nicht vollständig vorhanden. Wenn die Anwendung auf einem Host namens sun72.bht-berlin.de mit der IP-Adresse 141.64.89.72 gestartet worden ist, dann liegt nach dem Konstruktoraufruf folgende Socketinformation vor:

Socketinformation zu dem Datagrammsocket ds: Protocol : UDP Local Host : 141.64.89.72 Local Port : 8641 Remote Host : Remote Port :

Über diesen Socket kann zu diesem Zeitpunkt noch nicht gesendet werden, denn noch liegt kein Ziel vor. Aber die Anwendung kann an dem Socket auf den Eingang eines Datagramms warten. Dies erfolgt bei dem Versuch, mit einem receive()-Aufruf dem Socket ein Datagramm zu entnehmen:

ds.receive(dp);

Liegt an dem Socket ds ein Datagramm an, dann überträgt receive() dieses Datagramm in das vorbereitete (bisher leere) Datagramm dp und schneidet dabei einen eventuell zu großen Inhalt ab. Es ist möglich, dass schon vor der Abarbeitung des receive()-Befehls ein Datagramm den Socket erreicht hat. Dann findet kein Warten statt und das angekommene Datagramm wird unmittelbar in dp übertragen. Mit dem Datagramm erhält der Empfänger auch die Adresse des Senders, so dass damit seine Socketinformation vervollständigt wird. Um beispielsweise ein Echo zu realisieren, kann er ohne weitere Aktionen das Datagramm dp an den Sender zurückschicken:

ds.send(dp);

Der Empfänger kann das Datagramm aber auch zerlegen und auf die Einzelteile zugreifen. Die Klasse DatagramPacket stellt dafür geeignete Methoden zur Verfügung, zum Beispiel die folgenden:

dp.getData() Die übertragenen Daten als byte[] dp.getLength() Die Länge der übertragenen Daten als int dp.getAddress() Die IP-Adresse des Senders als InetAddress dp.getPort() Die Portnummer des Senders als int

Senderaktionen

Eine Anwendung, die ein Datagramm senden will, muss zunächst eines erstellen. Dazu muss sie die zu übertragenden Daten in ein Byte-Array bringen und Adressinformationen hinzufügen. Sie könnte folgendermaßen beginnen:

String s = "Hallo"; byte[] b = s.getBytes();

Die sendende Anwendung benötigt die IP-Adresse des Empfängerhosts und die Portnummer der empfangenden Anwendung auf diesem Host. Fehlt ihr auch nur eine dieser beiden Angaben, dann kann sie dem Empfänger kein Datagramm schicken! Sehr oft ist von einem Rechner lediglich der Domänennamen bekannt, benötigt wird jedoch die IP-Adresse. In so einem Fall kann sich eine Anwendung vom Domain Name System (DNS) die zugehörige IP-Adresse geben lassen. Die Klasse InetAddress aus dem java.net-Package enthält dafür eine nützliche Methode. Beispielsweise erhält man die IP-Adresse eines Hosts namens hrz.bht-berlin.de durch den folgenden Methodenaufruf:

InetAddress hostAddr = InetAddress.getByName("hrz.bht-berlin.de");

Die IP-Adresse der Anwendung, die das Datagramm erhalten soll, und deren Portnummer werden Bestandteile des zu erstellenden Datagramms:

DatagramPacket dp = new DatagramPacket(b, b.length, hostAddr, 8641);

Der erste Parameter des DatagramPacket-Konstruktors ist das Byte-Array mit den eigentlichen Daten, die zu übermitteln sind. Mit dem zweiten kann die Länge der zu übertragenden Daten aus dem Byte-Array b beschränkt werden. Dritter und vierter Parameter sind die IP-Adresse (hostAddr) und die Portnummer (8641) der Empfängeranwendung. Um dieses Datagramm absenden zu können, muss ein Datagrammsocket eingerichtet werden:

DatagramSocket ds = new DatagramSocket();

Die Abarbeitung dieses Konstruktors liefert nur einen Teil der zugehörigen Socketinformation. Der Konstruktor hat die lokale IP-Adresse einer Konfigurationsdatei entnommen und sich von der Portverwaltungssoftware der Internetprotokolle einen freien lokalen Port geben lassen. Aber der Teil, der die Empfängeranwendung adressiert, befindet sich im Datagramm. Zusammengenommen ist die Socketinformation jedoch vollständig, und das Datagramm kann abgeschickt werden:

ds.send(dp);

Programmbeispiel

Auch für das Arbeiten mit Datagram Sockets soll ein zusammenhängenden Beispiel vorgestellt werden. Dabei wird das bereits bei den Stream Sockets vorgestellte Echo-Beispiel auf Datagram Sockets übertragen. Kommunikation über Datagram Sockets folgt einem Peer-to-Peer-Kommunikationsmodell. Das heißt, dass keine der beiden Anwendungen als Server und die andere als Client angelegt werden muss. Aber eine der beiden kann die Rolle eines Echo-Servers übernehmen, die andere die eines Echo-Clients. Realisiert wurden die beiden Programme:

EchoVerlangen.java EchoGeben.java

Die Anwendung EchoVerlangen übernimmt die Rolle eines Echo-Clients. Sie wurde auf irgendeinem Rechner der Hochschule gestartet. EchoGeben bildet einen Server nach und wurde auf einem Host namens hrz.bht-berlin.de der Berliner Hochschule für Technik implementiert und dort an den Port 8888 gebunden. Beide Anwendungen können lokal getestet werden. Im Quellkode des Programms EchoVerlangen ist dazu die Stringvariable server auf den Wert "localhost" zu setzen.

EchoGeben enthält eine Endlosschleife, in der auf das Eintreffen eines Datagramms gewartet wird. Kommt ein Datagramm an, wird es an den Absender zurückgeschickt. Enthält es als Nutzdaten den String "quit", dann beendet sich die Anwendung. EchoVerlangen fordert den Benutzer auf, an der Konsole einen String einzugeben, der von EchoGeben zurückgegeben werden soll. Die Anwendung erstellt ein entsprechendes Datagramm, übergibt es ihrem Socket und wartet auf das Echo-Datagramm. Nach Erhalt gibt sie dessen Inhalt zur Kontrolle aus und beendet sich dann. Das heißt, sie beendet sich nach jedem erhaltenen Echo.



Zurück zum Inhaltsverzeichnis des Manuskripts verteilte Systeme