Das WebSocket Application Messaging Protocol ist ein Protokoll welches WebSockets nutzt um Funktionen wie RPC (Remote Procedure Call) und PubSub (Publish & Subscribe) anzubieten. Intern werden die Nutzdaten mit Hilfe von JSON transportiert. Mittlerweile existieren einige Implementierungen um WAMP (http://wamp.ws) einzusetzen. Für PHP gibt es z.B. Ratchet: (http://socketo.me) . Ratchet ist eine lose gekoppelte Bibliothek, die einem Entwickler die Möglichkeit gibt, eine bi-direktionale Echtzeitkommunikation zwischen Client und Server in PHP umzusetzen. Als Gegenstück für den Server kann die JavaScript-Bibliothek Autobahn (http://autobahn.ws/js) verwendet werden.
Vorraussetzungen für die Beispielimplementierung:
Features:
Screenshot Client:
Screenshot Server:
Server (PHP)
use Ratchet\ConnectionInterface as Conn;
use Ratchet\Wamp\WampServerInterface;
use Ratchet\Wamp\Topic;
class Test implements WampServerInterface
{
private static $iClientCount = 0;
public function onPublish(Conn $oConn, $oTopic, $aEvent, array $aExclude = array(), array $aEligible = array())
{
switch ($oTopic->getID())
{
case 'http://localhost/msg':
self::log("Nachricht "".$aEvent['data']."" empfangen!");
break;
case 'http://localhost/move':
self::log("Bewege Draggable-Element [".floatval($aEvent['left']).", ".floatval($aEvent['top'])."]");
break;
}
$oTopic->broadcast($aEvent); // Event an alle angemeldeten Clients verteilen.
}
public function onCall(Conn $oConn, $sID, $oFn, array $aParams)
{
switch ($oFn->getId())
{
case 'App:add': //App:add RPC
self::log("Remote Procedure Call");
return $oConn->callResult($sID, array('result' => intval($aParams[0])+intval($aParams[1]))); //Ausrechnen
break;
}
}
public function onOpen(Conn $oConn)
{
self::$iClientCount++;
self::log("Ein Client hat sich mit dem Server verbunden. Verbundene Clients: ".self::$iClientCount);
}
public function onClose(Conn $oConn)
{
self::$iClientCount--;
self::log("Ein Client hat sich vom Server getrennt. Verbundene Clients: ".self::$iClientCount);
}
public function onSubscribe(Conn $oConn, $oTopic)
{
$sStatus = "Ein Client hat sich am Topic '".$oTopic->getId()."' angemeldet.";
self::log($sStatus);
$oTopic->broadcast(array('data' => $sStatus));
}
public function onUnSubscribe(Conn $oConn, $oTopic)
{
$sStatus = "Ein Client hat sich vom Topic '".$oTopic->getId()."' abgemeldet.";
self::log($sStatus);
$oTopic->broadcast(array('data' => $sStatus));
}
public function onError(Conn $oConn, \Exception $e)
{
self::log("Ein Fehler ist aufgetreten!");
}
public static function log($sText)
{
echo date('H:i:s').": ".$sText."\n";
}
}
Client (JavaScript)
var output = $('#output');
var button_input = $('button, input');
var btnSubscribeToggle = $('#btnSubscribeToggle');
var draggable = $('#draggable');
var msg1 = $('#msg1');
var val1 = $('#val1');
var val2 = $('#val2');
window.onload = function()
{
button_input.attr('disabled', '');
ab.connect("ws://localhost:8000", // Verbindung herstellen...
function (session) // WAMP-Session erzeugt
{
sess = session; //WAMP-Session-Objekt speichern
sess.subscribe('http://localhost/move', onEvent);
output.val("Verbindung zum Server wurde hergestellt!\n"+output.val());
button_input.removeAttr('disabled');
session.prefix("App", "http://localhost/Test#"); // Präfix für Aufrufe festlegen
},
function (code, reason) // WAMP-Session beendet!
{
output.val("Verbindung zum Server wurde getrennt!\n"+output.val());
button_input.attr('disabled', '');
});
btnSubscribeToggle.toggle(
function()
{
sess.subscribe('http://localhost/msg', onEvent); // Event Abonnieren - Ruft die Callback-Funktion onEvent auf, sobald ein Event ausgelöst wurde.
btnSubscribeToggle.html("Abmelden").css("background-color", "red");
},
function()
{
sess.unsubscribe('http://localhost/msg'); //Event Abonnierung aufheben
btnSubscribeToggle.html("Anmelden").css("background-color", "lightgreen");
});
draggable.draggable(
{
start: function(event, ui)
{
sess.unsubscribe('http://localhost/move', onEvent); //Vom Topic Abmelden, solange das DIV bewegt wird.
},
stop: function(event, ui)
{
sess.subscribe('http://localhost/move', onEvent); //Wieder beim Topic Anmelden, wenn das DIV losgelassen wird.
},
drag: function(event, ui)
{
var position = $(this).offset();
sess.publish("http://localhost/move", {left: position.left, top: position.top}); //Sendet die aktuelle Position an alle am Topic angemeldeten Clients
}
});
};
function calc()
{
sess.call("App:add", val1.val(), val2.val()).then( // Führt einen Remote Procedure Call aus
function (res) // Wenn eine Antwort empfangen wurde
{
output.val("Ergebnis: "+res.result+"\n"+output.val()); // Anzeigen der Rückgabe
});
}
function publishEvent()
{
output.val("Nachricht wurde an das Topic "msg" versendet\n"+output.val());
sess.publish("http://localhost/msg", {data: msg1.val()}); //Nachricht versenden
}
function onEvent(topicUri, event) // Wird aufgerufen, sobald ein Event ausgelöst wurde
{
switch (topicUri)
{
case "http://localhost/msg":
output.val("Event - Topic: "+topicUri+" Daten: "+event.data+"\n"+output.val()); //Nachricht anzeigen
break;
case "http://localhost/move":
draggable.css({'top': event.top, 'left' : event.left}) //Draggable-Element verschieben
break;
}
}
GitHub: https://github.com/sklueh/WAMP-Example
Download: https://github.com/sklueh/WAMP-Example/zipball/master
Weiterführende Links:
Eine ganz gute Einführung zum Thema Websockets gibt es hier bei Heise Developer:
http://www.heise.de/developer/artikel/WebSocket-Annaeherung-an-Echtzeit-im-Web-1260189.html
Hallo Wolfgang, Danke für den Link.