Wie ich WebSockets in ein bestehendes PHP-System integriert habe

    In diesem Artikel wird am Beispiel des CleverStyle CMS dargelegt, wie sich ein für PHP uncharakteristisches Element wie Web-Sockets in ein bestehendes System integrieren lässt und welche Nuancen auftreten können.

    Bibliotheken


    Das Schreiben eines Servers und eines Clients für Web-Sockets ist sehr schwierig. Glücklicherweise gibt es kaum eine alternative Ratchet- Bibliothek , die einen Server für Web-Sockets bereitstellt. Unter der Haube werden mehrere Teile von ReactPHP und Guzzle verwendet (dies hängt auch von den Symfony-Komponenten ab, in diesem Fall erwiesen sie sich jedoch als vollständig redundant). Wir werden auch Pawl von Ratchet verwenden, dies ist ein Client für Web-Sockets.

    Architektur


    Da CleverStyle CMS mit mehreren Servern arbeiten kann, wurde beschlossen, diese Funktionalität hier zu unterstützen. Hierzu wird ein Serverpool angelegt. Es ist ein MEMORY-Typenschild in MySQL mit einer Spalte und einer öffentlichen Adresse für die Verbindung wie wss: //web.site/WebSockets (in MySQL zu speichern oder woanders ist nicht wichtig, Hauptsache, alle Server haben Zugriff auf dieselben Informationen). . Jeder gestartete Server fügt sich dem Pool hinzu, und wenn er nicht alleine da ist, stellt er als Master eine Verbindung zum ersten her (wenn einer vorhanden ist, wird er selbst zum Master). Wenn der Master nicht antwortet, wird er aus dem Pool entfernt und der nächste wird der Master, und alle stellen eine Verbindung zu ihm her. Dies sichert Sturzsicherheit und vollständige Dezentralisierung.

    Wenn einer der Server eine Nachricht vom Client empfängt, sendet er sie an den Master und der Master sendet sie an alle anderen. Der Einfachheit halber kommunizieren die Server auch über Web-Sockets miteinander. Wenn Sie eine Nachricht an einen Benutzer mit einer bestimmten ID senden müssen, wird diese unter anderem auch an den Master gesendet, und es spielt daher keine Rolle, mit welchem ​​Server und in wie vielen Registerkarten der Benutzer verbunden ist.

    Nachrichten senden und empfangen


    Das Nachrichtenformat wurde einfach gewählt:

    {
    	"action"  : "some/action",
    	"details" : []
    }
    

    Eine Aktion ist nur eine Zeichenfolge, es kann etwas Skalares als Teil geben, dann wird sie in ein Array mit einem Element konvertiert.

    Das Format wird häufig für das Senden von Client zu Server und von Server zu Client verwendet. Der einzige Unterschied besteht darin, dass die Aktion für den Client mit dem Suffix : error enden kann. Der Einfachheit halber können Sie zwei Rückrufe für den Client angeben: success und error .

    Auf dem Client werden Nachrichten von einer Reihe von Methoden des cs.WebSockets- Objekts empfangen und gesendet (das automatisch eine Verbindung zum Server herstellt und aufrechterhält und sich auch über die aktuelle Sitzung authentifiziert):

    • ein (Aktion, Rückruf, Fehler)
    • aus (Aktion, Rückruf, Fehler)
    • einmalig (Aktion, Rückruf, Fehler)
    • senden (Aktion, Details)

    Es ist so einfach, dass es wahrscheinlich nirgendwo einfacher ist. Da die übergebenen Teile ein Array sind, wird jedes Element als separates Argument übergeben.

    Auf dem Server ist alles etwas komplizierter. Eine Nachricht wird mit der Methode \ cs \ modules \ WebSockets :: send_to_clients ($ action, $ details, $ send_to, $ target) gesendet (Sie können nicht nur unter dem Server, sondern auch auf normalen Seiten senden, in diesem Fall wird eine Verbindung mit einer hergestellt von Servern, und die Nachricht wird im internen Format übertragen, wonach sie den Client erreicht).

    Mit zusätzlichen Argumenten können Sie angeben, welcher Benutzer oder welche Gruppe oder mehrere bestimmte Benutzer die Nachricht übermitteln müssen.

    Damit das Standardereignissystem verwendet wird, müssen Sie das WebSockets / {action} -Ereignis abonnieren(Die Anmeldung erfolgt beim Auftreten des Ereignisses WebSockets / register_action. ) Dabei wird {action} vom Client empfangen, und der aktuelle Benutzer (Absender), seine Sitzung und seine Sprache werden ebenfalls an das Ereignis gesendet.

    Hallo Servicebeispiel:

    on('WebSockets/register_action', function () {
    	// If `hello` action from user
    	Event::instance()->on('WebSockets/hello', function ($data) {
    		$Server = Server::instance();
    		// Send `hello` action back to the same user with the same content
    		if ($data['details']) {
    			$Server->send_to_clients(
    				'hello',
    				$data['details'],
    				Server::SEND_TO_SPECIFIC_USERS,
    				$data['user']
    			);
    		} else {
    			$Server->send_to_clients(
    				'hello:error',
    				$Server->compose_error(
    					400,
    					'No hello message:('
    				),
    				Server::SEND_TO_SPECIFIC_USERS,
    				$data['user']
    			);
    		}
    	});
    });
    ?>
    

    // Since everything is asynchronous - lets add handler first
    cs.WebSockets.once('hello', function (message) {
    	alert(message);
    });
    // Now send request to server
    cs.WebSockets.send('hello', 'Hello, world!');
    

    Alles ist sehr einfach.

    Server starten


    Hier ist es schon etwas komplizierter. Es wurde beschlossen, die Unterstützung sowohl über die Weboberfläche (es gibt sogar eine Schaltfläche im Admin-Bereich) als auch über die Befehlszeile zu starten. Wenn die Ausführung von Konsolenbefehlen in PHP verfügbar ist, startet die Webversion weiterhin eine Konsolenversion des Servers unter der Haube. Der Server überwacht den in den Einstellungen für 0.0.0.0 oder 127.0.0.1 angegebenen Port zur Auswahl (dann sendet Nginx oder was auch immer an seiner Stelle ist, alle Verbindungen an wss: //web.site/WebSockets an diesen Port , das heißt, der Benutzer verwendet diesen 80 oder 443 Ports, andere Ports können in vielen Situationen blockiert werden (z. B. öffentliches WLAN).

    Der Web-Socket-Server verfügt über eine einfache Supervisor-Option - ein zusätzlicher doppelter Prozess, der prüft, ob der Server in Ordnung ist. In der Konsolenversion wird der Server sofort neu gestartet, wenn der Prozess abstürzt, in der Webversion wird mit einem Intervall von 10 Sekunden überprüft, ob der Server über eine Testverbindung aktiv ist, andernfalls wird er neu gestartet.

    Die Engine ist so ausgelegt, dass der Kernel startet, dann ein bestimmtes Modul entsprechend der Seite ausgeführt wird und dann die Schlussfolgerung gezogen wird. Der Web-Socket-Server ist also ein gewöhnliches Modul, erreicht jedoch nicht die Ausgabe. Die Ereignisschleife wird gestartet und wartet auf Verbindungen.

    Einige Fallstricke


    1. Es kann nur eine Ereignisschleife geben. In dieser Hinsicht verwenden sowohl der Client als auch der Server (da beide bei der Kommunikation mit dem Master / Mitgliedsserver Nachrichten senden und empfangen können) dieselbe Ereignisschleife
    2. Alle vorbereitenden Aktionen müssen durchgeführt werden, bevor der Server gestartet wird. Nach dem Start der Ereignisschleife wird der gesamte lineare Code blockiert, bis er stoppt (er schneidet sich mit dem ersten Element, aber immer noch).
    3. Es ist notwendig, das Gedächtnis zu schützen; Wenn sich in den Objekten Caches befinden, müssen Sie diese deaktivieren. Andernfalls kann der Prozess nach einigen Tagen zu viel Speicher verbrauchen. Sie können die Situation überprüfen, indem Sie den Server beispielsweise mit Siege laden
    4. Zeit sparen müssen; Da Sie höchstwahrscheinlich nicht für alle Funktionen nicht blockierende alternative Optionen verwenden, wird der nächste Client erst gewartet, wenn ein Blockierungsvorgang ausgeführt wird. Minimieren Sie daher die Arbeit unter dem Server
    5. Bereiten Sie Ihren Code für die Langzeitausführung vor (in meinem Fall wurde an einigen Stellen eine Konstante für die Startzeit des Skripts verwendet, die im Falle eines langen Abspielvorgangs nicht mehr relevant war).

    Das ist alles


    Obwohl Web-Sockets und ähnliche asynchrone Dinge in PHP nicht "nativ" sind und wenn sie oft erwähnt werden, wird auch Node.js erwähnt, sowie wie man es mit PHP kombiniert. Letztere selbst können leicht mit demselben Paradigma arbeiten und sterben nicht danach jede Anfrage (mit bestimmten Fähigkeiten können Sie React verwenden, um einen HTTP-Server in reinem PHP zu erstellen ).

    Um es zu versuchen


    Wenn Sie spielen möchten, wartet der Docker-Container auf Sie:

    docker run --rm -p 8888:8888 -v /some_dir:/web nazarpc/cleverstyle-cms
    

    Gehen Sie dann zu localhost: 8888 im Browser, schalten Sie das Web-Socket-Modul ein und starten Sie den Server. Fügen Sie im Ordner / some_dir beliebige Module hinzu, experimentieren Sie, versuchen Sie, alles wird sofort in die Demo aufgenommen. Nach dem Anhalten müssen Sie nur noch / some_dir löschen (der Schalter --rm löscht den Container selbst automatisch).

    Quellcode

    Jetzt auch beliebt: