XMPP mit ActiveMQ statt Drittanbieterdienste

Push – jetzt auch in Grün!
Kommentare

Immer mehr Unternehmen nutzen das Potenzial mobiler Endgeräte (Smartphones und Tablets) und binden sie in ihre Geschäftsprozesse, bzw. in ihr Geschäftsmodell ein. Mitarbeiter können mithilfe von Apps Aufgaben schnell, einfach und standortunabhängig erledigen. Je nach Anwendungsfall müssen sie dabei zuverlässig und zeitnah über Veränderungen informiert werden – unabhängig davon, ob eine bestimmte App gerade im Vordergrund läuft oder aktiv genutzt wird. Push-Benachrichtigungen stellen hier eine elegante und effiziente Lösung dar, da Nachrichten schnell zugestellt werden können, während gleichzeitig der Akku geschont wird. In diesem Artikel wird eine Lösung für Push-Benachrichtigungen auf Basis des XMPP-Protokolls und des Message Broker ActiveMQ vorgestellt. Dies stellt insbesondere dann eine Alternative da, wenn man sich bei der Kommunikation nicht auf Dienste von Drittanbietern (wie beispielsweise Googles C2DM oder GCM) verlassen möchte.

Mobile Endgeräte sind allgegenwärtig und immer häufiger Teil von Geschäftsprozessen und Geschäftsmodellen. Einzelne Aufgaben können mithilfe von spezialisierten Apps einfach und zielgerichtet auf mobilen Geräten und damit unabhängig von einem stationären Arbeitsplatz durchgeführt werden. Gerade bei Geschäftsanwendungen spielt die Aktualität der Daten dabei eine erhebliche Rolle. Der klassische Ansatz des Pollings, also das regelmäßige Abrufen von Informationen, stößt bei mobilen Endgeräten jedoch an seine Grenzen. Ist die Abrufrate zu gering, werden Informationen zu spät kommuniziert – ist sie zu hoch, wird der Akku zu stark belastet.

Push-Benachrichtigungen stellen eine Alternative zum Polling dar, wenn Änderungen zuverlässig und energieeffizient kommuniziert werden müssen. Neben Push-Benachrichtigungen über SMS (welche mit erheblichen Kosten verbunden sein können) existieren mit Googles Android Cloud to Device Messaging (C2DM) und dessen Nachfolger Google Cloud Messaging for Android (GCM) weit verbreitete Lösungen. Diese sind jedoch, insbesondere für den professionellen Einsatz, mit diversen Nachteilen verbunden. So können zum einen im Payload der Nachricht nur kleine Datenmengen versendet werden. Sollen beispielsweise Bilder übertragen werden, müssen sie auf anderem Wege nachgeladen werden. Außerdem kann eine kurze Dauer oder bestimmte Reihenfolge der Nachrichtenzustellung auch bei dauerhaft vorhandener Verbindung nicht garantiert werden. Das womöglich wichtigste Argument gegen den Einsatz dieser Dienste im Geschäftsumfeld ist jedoch, dass potenziell sensible Daten über Dienste von Drittanbietern versandt werden und ein Google-Account zwingend erforderlich ist.

XMPP statt Drittanbieterdienste

Eine Alternative stellt die Realisierung von Push-Benachrichtigungen über das Extensible Messaging and Presence Protocol, kurz XMPP, dar. Es handelt sich dabei um ein auf XML basierendes Protokoll mit offen zugänglicher Dokumentation. Diverse, quelloffene Projekte stellen XMPP-Clients und -Bibliotheken für nahezu jedes Endsystem bereit. Früher unter dem Namen Jabber bekannt, wird XMPP hauptsächlich zum Instant Messaging – unter anderem von Facebook Messenger oder Google Talk – verwendet. An unserem Institut wird XMPP in diversen Kontexten eingesetzt, unter anderem beim Mobile Device Management und in einem Logistikszenario zur Benachrichtigung von LKW-Fahrern bei Routenänderungen oder Stauinformationen.

Hands-on: Umsetzung am Beispiel

Am Beispiel (Abb. 1) erläutern wir im Folgenden, wie man XMPP unter Android verwendet und ActiveMQ als Message Broker entsprechend konfiguriert. In unserem Fall wird XMPP lediglich zur unidirektionalen Kommunikation – vom Server zur App – verwendet. Dies lässt sich allerdings leicht erweitern, sodass auch die App selbst Nachrichten über XMPP versendet.

Abb. 1: Beispielanwendung bestehend aus Android-App, ActiveMQ und Java Backend

Nachrichtenversand über ActiveMQ

ActiveMQ implementiert den Java Message Service 1.1 (JMS) und unterstützt neben Java noch eine Reihe weiterer Programmiersprachen. Weiterhin bietet ActiveMQ eine Vielzahl von Schnittstellen (Connectors) an, um unterschiedliche Protokolle (u. a. XMPP, MQTT und Stomp) zu unterstützen. ActiveMQ kann von der Apache-Webseite als Archiv für Windows und Unix heruntergeladen werden und nach dem Entpacken über den Befehl ./bin/activemq start gestartet werden. Mithilfe der Connectors können dann verschiedene Systeme angebunden werden. Standardmäßig ist nur der OpenWire Connector aktiviert. Weitere Connectors (in unserem Fall XMPP) können aktiviert werden, indem man sie als transportConnector in der Datei ./conf/activemq.xml ergänzt (Listing 1). Die derzeit aktiven Schnittstellen können über die ActiveMQ-Webkonsole (http://localhost:8161/admin) eingesehen werden.

<transportConnectors>
  <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/>
  <!-- XMPP aktivieren -->
  <transportConnector name="xmpp" uri="xmpp://0.0.0.0:61222"/>
</transportConnectors>

ActiveMQ stellt zwei verschiedene Nachrichtenmodelle zur Verfügung: Point to Point und Publish/Subscriber. Beim Point-to-Point-Modell wird jede Nachricht an genau einen Empfänger gesendet. Die Nachrichten landen zunächst in einer Warteschlange (Queue), bis der Empfänger sie verarbeitet. Im Gegensatz dazu können beim Publish/Subscriber-Modell mehrere Empfänger dieselbe Nachricht erhalten. Dazu registrieren (subscriben) sie sich für ein bestimmtes Topic. Jede Nachricht des Topics wird dann allen Subscribern dieses Topics zugestellt. In unserem Beispiel wird das Publish/Subscriber-Modell verwendet. Falls die Topics nicht bereits vorkonfiguriert sind (siehe unten) werden sie von ActiveMQ bei ihrer ersten Verwendung dynamisch erzeugt. Die Liste aller Topics kann auf der ActiveMQ-Webkonsole (siehe oben) angezeigt werden.

Aufmacherbild: Android posing in sunglasses. Technology concept von Shutterstock / Urheberrecht: Kirill__M

[ header = Android-Client]

Android-Client

Auf der Gegenseite steht der Android-Client, der sich für ein bestimmtes Topic registriert. Um XMPP unter Android nutzen zu können, wird zunächst eine entsprechende Bibliothek benötigt, beispielsweise Android Beem. Um Probleme mit verschiedenen SDK-Versionen zu vermeiden, sind wir dazu übergegangen, die Bibliotheken direkt als Quellcode ins Projekt einzubinden. Dies ist aber nicht zwangsläufig notwendig.

Der Client besteht aus einer Activity, einem Service und mehreren BroadcastReceivern. Die Activity erlaubt es dem Nutzer, Verbindungseinstellungen vorzunehmen und die XMPP-Verbindung zu starten oder zu stoppen (Abb. 2). Der Service, im weiteren Verlauf „XmppService“ genannt, hält die eigentliche XMPP-Verbindung zum Server. Eingehende Push-Benachrichtigungen und der Status der Verbindung werden von den registrierten BroadcastReceivern verarbeitet und dem Nutzer mithilfe von Notifications angezeigt (Abb. 3). Der vollständige Quellcode der Anwendung findet sich hier.

Abb. 2: Die Client-App

Abb. 3: Status und Benachrichtigungen

Beim Klick auf den Start-Button werden die eingegebenen Verbindungseinstellungen an den XmppService übergeben und dieser wird gestartet. Die XMPP-Verbindung wird anschließend automatisch herstellt. Beim Klick auf den Stop-Button wird entsprechend die XMPP-Verbindung beendet und der Service wieder gestoppt (Listing 2).

// Start button clicked
public void enable(View view) {
  Intent i = new Intent(this, XmppService.class);
  i.putExtra("username", mUsername.getText().toString());
  i.putExtra("password", mPassword.getText().toString());
  i.putExtra("host", mHost.getText().toString());
  i.putExtra("port", Integer.parseInt(mPort.getText().toString()));
  startService(i);
  save();
}
// Stop button clicked
public void disable(View view) {
  stopService(new Intent(this, XmppService.class));
}

Der XmppService sollte stets „sticky“ gestartet werden, damit er automatisch wieder gestartet wird, falls er zuvor aufgrund eines Speichermangels beendet worden sein sollte. Dazu muss die onStartCommand-Methode lediglich die Konstante Service.START_STICKY zurückgeben. Der XmppService selbst besteht im Wesentlichen aus drei Teilen:

  • der XMPPConnection
  • einem PacketFilter, der eingehende Pakete beliebig filtern kann
  • einem PacketListener, der die eingehenden Nachrichten letztendlich empfängt

Die Verbindung muss, wie alle Netzwerkoperationen, in einem eigenen Thread ausgeführt werden, da sie sonst den Haupt-Thread blockieren würde. Zum Verbindungsaufbau wird zunächst die XMPPConnection unter Angabe der Server-IP und des Server-Ports initialisiert (Listing 3). Anschließend wird die Verbindung hergestellt und das Gerät durch einen Login für ein entsprechendes Topic registriert. Hat alles funktioniert, wird der Status auf „available“ gesetzt und die Verbindung steht (Listing 4). Die Notwendigkeit des letzten Schritts zeugt hierbei vom Ursprung des XMPP, dem Instant Messaging.

// initialize
if (mConnection == null) {
  mConnectionConfiguration = new ConnectionConfiguration(mHost, mPort);
  mConnectionConfiguration.setReconnectionAllowed(true);
  mConnection = new XMPPConnection(mConnectionConfiguration);
}
// connect
if (!mConnection.isConnected()) {
  mConnection.connect();
  mConnection.addConnectionListener(new XmppReconnectionManager(mConnection));
  mConnection.addPacketListener(mListener, mFilter);
}

// login
if (mConnection.getUser() == null) {
  mConnection.login(mUsername, mPassword);
}

// set status to available
Presence presence = new Presence(Presence.Type.available);
mConnection.sendPacket(presence);

Um eingehende Nachrichten zu verarbeiten, müssen ein PacketFilter und ein PacketListener registriert werden, deren Umsetzung dem Entwickler überlassen bleibt. In unserem Fall werden alle Nachrichten des Typs Message verarbeitet und als Broadcast zur Weiterverarbeitung an registrierte BroadcastReceiver gesendet (Listing 5).

private PacketListener mListener = new PacketListener() {
  @Override
  public void processPacket(Packet packet) {
    Intent intent = new Intent(Constants.XMPP_MSG_RECEIVED);
    intent.putExtra("msg", ((Message) packet).getBody());
    sendBroadcast(intent);
  }
};
private PacketFilter mFilter = new PacketFilter() {
  @Override
  public boolean accept(Packet packet) {
    return packet != null && packet instanceof Message;
  }
};

Gerade wenn man unterwegs ist und kein WLAN in Reichweite ist, sind Verbindungsabbrüche an der Tagesordnung. Je nach verwendeter Bibliothek und Konfiguration wird automatisch versucht, die Verbindung wiederherzustellen. Wer sich darauf nicht verlassen will, sollte selbst Hand anlegen. Dazu eignen sich ebenfalls BroadcastReceiver, die bei Veränderung der Netzwerkkonnektivität entsprechend reagieren (Listing 6). Je nach Anforderungen kann man Nachrichten nach Verbindungsabbrüchen auch durch Verwendung von Durable-Topics nachliefern lassen.

private class NetworkConnectivityListener extends BroadcastReceiver {
  @Override
  public void onReceive(Context ctx, Intent intent) {
    requestWakeLock();
    if (isDeviceOnline()) {
      connect();
    } else {
      setConnectionStatus(
            XmppConnectionStatus.NOTCONNECTED_WAITINGFORINTERNET);
      disconnect();
    }
    releaseWakeLock();
  }
}

[header = Versenden einer Nachricht ]

Versenden einer Nachricht

Bisher wurde nur der Nachrichtenempfang auf dem Smartphone beschrieben, nicht aber der Versand einer Push-Benachrichtigung. Dieser kann je nach Bedarf vom Smartphone oder von einem Backend-System geschehen. Da ActiveMQ viele Protokollerweiterungen unterstützt, kann es problemlos mit verschiedenen existierenden Systemen kommunizieren. In Java kann man sich beispielsweise via JMS mit dem ActiveMQ-Server verbinden und seine Nachricht auf das jeweilige Topic versenden (Listing 7).

private static String serverURL = "tcp://127.0.0.1:61616";
private static String username = "writer";
private static String password = "pass";

public static void main(String[] args) {
  try {
    String msg = "Hello XMPP!";
    String topic = "reader";
    ConnectionFactory connectionFactory = 
      new ActiveMQConnectionFactory(serverURL);
    Connection connection = 
      connectionFactory.createConnection(username, password);
    connection.start();

    Session session = 
      connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    MessageProducer messageProducer = 
      session.createProducer(session.createTopic(topic));
    Message message = session.createTextMessage(msg);
    messageProducer.send(message);
    connection.close();
  } catch (Exception e) {
    e.printStackTrace();
  }
}

Sicher ist sicher – Benutzerauthentifizierung unter ActiveMQ

In der Standardkonfiguration von ActiveMQ sind keinerlei Sicherheitsmechanismen aktiv, wodurch die einzelnen Topics ungeschützt sind. Eine einfache Benutzerauthentifizierung lässt sich in der Datei ./conf/activemq.conf einrichten. So können sich nur eingetragene Benutzer mit dem Server verbinden. Dazu fügt man im <broker>-Tag einen Abschnitt <plugins> (Listing 8) ein. Mit dem simpleAuthentificationPlugin lassen sich anschließend Nutzer mit Passwort und Nutzergruppe anlegen.

<plugins>
...
  <!-- Auth: Benutzer und Gruppen --> 
  <simpleAuthenticationPlugin anonymousAccessAllowed="false">
    <users>
      <authenticationUser username="reader" password="pass" groups="consumers"/>
      <authenticationUser username="writer" password="pass" groups="producers"/>
      <authenticationUser username="admin" password="pass" groups="admins"/>
    </users>
  </simpleAuthenticationPlugin>
...
</plugins>

Für alle eingetragenen Nutzergruppen sollten nun Zugriffsberechtigungen verteilt werden. Hierzu wird ein weiterer Eintrag im <plugin>-Tag vorgenommen. Wichtig hierbei ist ein Eintrag authorizationEntry mit ActiveMQ.Advisory. Hier sollten laut Dokumentation jeder Gruppe die vollen Rechte gewährt werden [7]. Durch weitere authorizationEntry-Einträge lassen sich die Zugriffsrechte für bestimmte Topics festlegen, in Listing 9 für das Topic topic.

<plugins>
...
<!-- Berechtigungen für die Gruppen festlegen -->
  <authorizationPlugin>
    <map>
      <authorizationMap>
        <authorizationEntries>
         <authorizationEntry topic="ActiveMQ.Advisory"
               read="consumers,producers,admins"
               write="consumers,producers,admins"
               admin="consumers,producers,admins"/>
         <authorizationEntry topic="topic" 
               read="consumers,producers" 
               write="producers" 
               admin="admins"/>  
         </authorizationEntries>
      </authorizationMap>
    </map>
  </authorizationPlugin>
...
</plugins>

ActiveMQ kann die Topics bei Bedarf erstellen, sobald auf sie zugegriffen wird. Da der Benutzer reader aber selbst keine Schreibrechte auf das Topic topic hat, sollte dafür gesorgt werden, dass dieses beim Zugriff bereits existiert. Dafür kann das broker-Tag in der ./conf/activemq.conf um den Bereich destinations erweitert werden (Listing 10). Hier lassen sich feste Topics angeben, die immer verfügbar sind.

<broker ..>
...
  <!-- Berechtigungen für die Gruppen festlegen -->
  <destinations>
    <topic physicalName="topic"/>
  </destinations>
...
</broker>

Sparsam, schnell und zuverlässig?

Der Hauptgrund für den Einsatz von Push-Technologien ist deren Energieeffizienz. Hier haben Googles Dienste einen Heimvorteil – denn eine Verbindung zu Googles Servern besteht quasi immer, sobald ein Google-Konto eingerichtet ist. Da dies bei fast allen Geräten der Fall ist, ist der Einsatz von C2DM/GCM quasi gratis bezüglich des Energieverbrauchs (zumindest solange keine Nachrichten empfangen werden). In der Praxis erweist sich XMPP jedoch als sehr energieeffizient, sodass der beobachtete Energieverbrauch im Standby kaum wahrnehmbar ist.

Fazit

Push-Benachrichtigungen stellen ein geeignetes Mittel dar, um Daten schnell auf mobile Endgeräte zu verteilen, ohne den Akku unnötig zu belasten. Wer sich dabei nicht auf die Dienste Dritter verlassen möchte, sollte die Umsetzung selbst in die Hand nehmen. Mit XMPP als plattformunabhängigem Protokoll gelingt dies in der Praxis schnell und zuverlässig.

Dank diverser, quelloffener Bibliotheken ist die Konfiguration und Nutzung kaum aufwändiger als jene fremder Dienste und zudem weitaus flexibler. Wer auf der Suche nach einer noch leichtgewichtigeren Lösung ist, sollte sich MQTT genauer ansehen. Im Gegensatz zu XMPP wird bei MQTT das Nachrichtenformat nicht vorgegeben und kann selbst gewählt werden, während bei XMPP stets XML verwendet wird. Im Gegenzug bietet XMPP jedoch bessere Unterstützung und eine größere Community.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -