PHP: Event-Driven Programming mit WAMP (WebSockets)

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:

  • PHP 5.4 Interpreter (idealerweise XAMPP auf Laufwerk C:)
  • Browser mit WebSocket-Support
  • Features:

  • An Topic anmelden / abmelden
  • Nachricht versenden / empfangen
  • Remote Procedure Call
  • Über WebSockets bewegliches DIV
  • Screenshot Client:
    WAMP Beispielimplementierung Client
    Screenshot Server:
    WAMP Beispielimplementierung Server


    Server (PHP)

    namespace MyApp;
    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 sess; // WAMP-Session-Objekt
        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:

  • http://wamp.ws
  • http://socketo.me
  • http://autobahn.ws/js
  • 2 Gedanken zu „PHP: Event-Driven Programming mit WAMP (WebSockets)“

    Schreibe einen Kommentar