UDP und Multicast mit Java Sockets

Martin Kompf

Der Artikel Netzwerkclients in Java demonstriert, wie einfach die Verwendung von Internetressourcen in einem Java Programm ist. Nun soll ein ambitionierteres Projekt in Angriff genommen werden: Die Implementierung des Simple Service Discovery Protocol (SSDP), welches Bestandteil von Universal Plug and Play (UPnP) ist.

Ohne Konfiguration

Universal Plug and Play (UPnP) dient zur einfachen und herstellerübergreifenden Vernetzung von Geräten insbesondere im Multimediabereich. Es ermöglicht zum Beispiel die Steuerung von Mediaplayern zum Abspielen von Audio- oder Videostreams, die von einem Mediaserver stammen. Damit sich Fernsteuerung, Player und Server ohne manuelle Konfiguration gegenseitig finden, bedienen sie sich des Simple Service Discovery Protocol (SSDP).

Eine wesentliche Komponente von SSDP ist die Kommunikation über die Multicast Adresse 239.255.255.250:1900. Multicast erlaubt es mehreren Netzwerkendpunkten untereinander zu kommunizieren. Ein an eine Multicast Adresse zugestelltes Netzwerkpaket erreicht alle Endpunkte, die sich auf diese Adresse registriert haben. Das hierbei verwendete Transportprotokoll ist UDP (User Datagram Protocol). Es enthält keine Flusskontrolle, das heißt es gibt keine Rückmeldung an den Sender des Pakets, ob es bei allen Empfängern angekommen ist. Insbesondere muss der Sender auch gar nicht alle seine Empfänger kennen. Im Gegensatz dazu läuft der Großteil der Kommunikation im Internet - zum Beispiel HTTP - per Unicast ab und verwendet das Transportprotokoll TCP. Hierbei besteht eine Netzwerkverbindung immer genau aus zwei Teilnehmeŕn (Client und Server) und es erfolgt eine Quittierung jedes einzelnen empfangenen Pakets zur Flusskontrolle.

Multicast Socket

In Java dient zur Realisierung von Multicast die Klasse java.net.MulticastSocket. Durch Aufruf der Methode joinGroup registriert sich das Javaprogramm als Teilnehmer einer Multicast Kommunikation auf der als Parameter übergebenen Adresse:

import java.io.*;
import java.net.*;

public class SSDPNetworkClient {
  /**
   * UPNP/SSDP client to demonstrate the usage of UDP multicast sockets.
   * @throws IOException
   */
  public void multicast() throws IOException {
    try {
      InetAddress multicastAddress = InetAddress.getByName("239.255.255.250"); 
      // multicast address for SSDP
      final int port = 1900; // standard port for SSDP
      MulticastSocket socket = new MulticastSocket(port); 
      socket.setReuseAddress(true);
      socket.setSoTimeout(15000);
      socket.joinGroup(multicastAddress);

Senden und Empfangen

Das MulticastSocket eignet sich zum Lesen und Schreiben von UDP Paketen, die durch die Klasse java.net.DatagramPacket gekapselt werden. Bei einer reinen Lesefunktionalität würde das Programm lediglich alle SSDP Nachrichten empfangen, die zufällig durch das lokale Netzwerk laufen. Um dagegen alle im Netzwerk vorhanden UPnP Geräte möglichst schnell zu einer Reaktion zu veranlassen, sendet das Programm zunächst ein SSDP Discover Paket:

      // send discover
      byte[] txbuf = DISCOVER_MESSAGE_ROOTDEVICE.getBytes("UTF-8");
      DatagramPacket hi = new DatagramPacket(txbuf, txbuf.length,
          multicastAddress, port);
      socket.send(hi);
      System.out.println("SSDP discover sent");

Anschließend geht das Programm in den Empfangsmodus. In einer Schleife werden von den UPnP Geräten gesendeten Antworten auf das Discover Paket aus dem Socket gelesen und ausgegeben. Die scheinbare Endlosschleife kann durch eine IOException im Falle eines Fehlers oder durch eine SocketTimeoutException verlassen werden:

      do {
        byte[] rxbuf = new byte[8192];
        DatagramPacket packet = new DatagramPacket(rxbuf, rxbuf.length);
        socket.receive(packet);
        dumpPacket(packet);
      } while (true); // should leave loop by SocketTimeoutException
    } catch (SocketTimeoutException e) {
      System.out.println("Timeout");
    }
  }

Das Socket Timeout hatten wir weiter oben bei der Erzeugung des MulticastSocket per Methode setSoTimeout auf 15 Sekunden eingestellt.

Zur Vervollständigung des SSDP Clients fehlen noch die Methode dumpPacket zur Ausgabe des empfangenen UDP Paketes und die Definition der SSDP Discover Message:

  private void dumpPacket(DatagramPacket packet) throws IOException {
    InetAddress addr = packet.getAddress();
    System.out.println("Response from: " + addr);
    ByteArrayInputStream in = new ByteArrayInputStream(packet.getData(), 0, packet.getLength());
    copyStream(in, System.out);
  }

  private void copyStream(InputStream in, OutputStream out) throws IOException {
    BufferedInputStream bin = new BufferedInputStream(in);
    BufferedOutputStream bout = new BufferedOutputStream(out);
    int c = bin.read();
    while (c != -1) {
      out.write((char) c);
      c = bin.read();
    }
    bout.flush();
  }

  private final static String DISCOVER_MESSAGE_ROOTDEVICE =
    "M-SEARCH * HTTP/1.1\r\n" +
    "ST: upnp:rootdevice\r\n" +
    "MX: 3\r\n" +
    "MAN: `ssdp:discover`\r\n".replace('`', '"') +
    "HOST: 239.255.255.250:1900\r\n\r\n";

  /**
   * MAIN
   */
  public static void main(String[] args) throws Exception {
    SSDPNetworkClient client = new SSDPNetworkClient();
    client.multicast();
  }
}

Erforsche Dein Netz!

Wie man dem Quellcode entnehmen kann, handelt es sich bei der SSDP Discover Message um einen HTTP Request - nur dass dieser nicht per TCP, sondern über UDP übertragen wird. Man spricht deshalb auch von HTTPU. Ebenso erfolgen die Antworten per HTTPU. Lässt man das Programm einmal im Heimnetzwerk laufen, erhält man aufschlussreiche Ergebnisse:

Response from: /192.168.178.1
HTTP/1.1 200 OK
LOCATION: http://192.168.178.1:49100/MediaServerDevDesc.xml
SERVER: FRITZ!Box Fon WLAN 7170 (UI) UPnP/1.0 AVM FRITZ!Box Fon WLAN 7170 (UI) 29.04.87
CACHE-CONTROL: max-age=1800
EXT:
ST: upnp:rootdevice
USN: uuid:xxxxxx-xxxx-xxxx::upnp:rootdevice

Response from: /192.168.178.20
HTTP/1.1 200 OK
Ext:
Date: Wed, 26 May 2004 01:27:28 GMT
ST:upnp:rootdevice
USN:uuid:yyyyyyyy-yyyyyy-yyyy::upnp:rootdevice
Location: http://192.168.178.20:80/DeviceDescription.xml
Cache-Control: max-age=900
Server: Allegro-Software-RomPlug/4.32 UPnP/1.0 RomPlug4.32
Content-Length: 0

Im obigen Beispiel antworten zwei Geräte auf den Adressen 192.168.178.1 und 192.168.178.20. Man sollte nur Antworten von Geräten aus dem eigenen Netzwerk sehen - Multicast Adressen werden in der Regel nicht über Netzwerkgrenzen hinweg geroutet! Der Response Body enthält diverse Parameter, von denen Location zweifellos der wichtigste ist. Sein Wert ist eine URL, über die sich die genaue Spezifikation der Fähigkeiten des UPnP Gerätes abfragen lässt. Eine Interpretation sprengt den Rahmen dieses Artikels, für weitergehende Informationen sei auf die unten stehenden Links verwiesen.