Evénements envoyés par le serveur et PHP - Qu'est-ce qui déclenche des événements sur le serveur?

91

Tout,

HTML5 Rocks a un joli tutoriel pour débutants sur les événements envoyés par le serveur (SSE):

http://www.html5rocks.com/en/tutorials/eventsource/basics/

Mais, je ne comprends pas un concept important - qu'est-ce qui déclenche l'événement sur le serveur qui provoque l'envoi d'un message?

En d'autres termes - dans l'exemple HTML5 - le serveur envoie simplement un horodatage une fois :

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));

Si je construisais un exemple pratique - par exemple, un «mur» de style Facebook ou un stock-ticker, dans lequel le serveur «poussait» un nouveau message au client à chaque fois qu'une donnée change, comment cela fonctionne-t-il?

En d'autres termes ... Le script PHP a-t-il une boucle qui s'exécute en continu, vérifiant un changement dans les données, puis envoyant un message à chaque fois qu'il en trouve un? Dans l'affirmative, comment savoir quand mettre fin à ce processus?

Ou - le script PHP envoie-t-il simplement le message, puis se termine (comme cela semble être le cas dans l'exemple HTML5Rocks)? Si oui, comment obtenir des mises à jour continues? Le navigateur interroge-t-il simplement la page PHP à intervalles réguliers? Si oui - comment est-ce un "événement envoyé par le serveur"? En quoi est-ce différent d'écrire une fonction setInterval en JavaScript qui utilise AJAX pour appeler une page PHP à intervalle régulier?

Désolé - c'est probablement une question incroyablement naïve. Mais aucun des exemples que j'ai pu trouver ne le précise.

[METTRE À JOUR]

Je pense que ma question était mal formulée, alors voici quelques éclaircissements.

Disons que j'ai une page Web qui devrait afficher le prix le plus récent de l'action Apple.

Lorsque l'utilisateur ouvre la page pour la première fois, la page crée un EventSource avec l'URL de mon "flux".

var source = new EventSource('stream.php');

Ma question est la suivante: comment "stream.php" devrait-il fonctionner?

Comme ça? (pseudo-code):

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
    function sendMsg($msg) {
        echo "data: $msg" . PHP_EOL;
        echo PHP_EOL;
        flush();
    }

    while (some condition) {
        // check whether Apple's stock price has changed
        // e.g., by querying a database, or calling a web service
        // if it HAS changed, sendMsg with new price to client
        // otherwise, do nothing (until next loop)
        sleep (n) // wait n seconds until checking again
    }
?>

En d'autres termes, "stream.php" reste-t-il ouvert tant que le client y est "connecté"?

Si tel est le cas, cela signifie-t-il que vous avez autant de threads en cours stream.phpd' exécution que d'utilisateurs simultanés? Si tel est le cas, est-ce possible à distance ou est-ce une manière appropriée de créer une application? Et comment savoir quand vous pouvez METTRE FIN à une instance de stream.php?

Mon impression naïve est que, si tel est le cas, PHP n'est pas une technologie adaptée à ce type de serveur. Mais toutes les démos que j'ai vues jusqu'à présent impliquent que PHP est très bien pour cela, c'est pourquoi je suis si confus ...

mattstuehler
la source
C'est la partie qu'un développeur doit coder seul. Les moyens d'obtenir les données se font via des websockets / une longue interrogation, etc., mais le truc est ce qui déclenche l'événement. Personnellement, j'ai expérimenté quelques approches et une approche qui m'a plu (mais ce n'était pas si sûr) était de faire en sorte que MySQL déclenche un programme de console à chaque fois que quelque chose était inséré dans une table spécifique. Le programme de la console recevrait les informations sur l'enregistrement modifié / inséré et il enverrait une notification à l'utilisateur correspondant via WebSockets. En gros, j'avais un démon PHP qui attendait d'envoyer des messages.
NB
Un problème avec cela, SSE n'est pas pris en charge par IE: - / Aussi je lirais ce prodigyproductionsllc.com/articles/programming/javascript/ ... Je pense qu'il utilise un port pour éviter le problème du trop-grand nombre d'enfants mais dans l'ensemble ressemble à son la recommandation est d'éviter l'ESS. On dirait bien plus de problèmes que ça ne vaut, IMO.
PJ Brunet
Actuellement non pris en charge par IE11 ou le navigateur Android caniuse.com/eventsource
PJ Brunet
1
Si quelqu'un a besoin de code php sse: github.com/shahzadthathal/server-sent-events-php-example
Muhammad Shahzad
4
J'ai eu la même question et je pense que je comprends profondément ce que vous entendez par ce qui déclenche l'événement sur le serveur ... . Lorsque vous créez un objet de EventSource('stream.php'), le client ouvre une connexion avec stream.phplaquelle revient à l'appeler par ajax. CETTE connexion déclenche votre code côté serveur et maintient la connexion ouverte tant que votre code côté serveur a quelque chose à dire. Ensuite, la connexion se ferme et après un court délai (3 secondes en chrome je pense), le client rouvre la connexion ce qui déclenche stream.phpà nouveau votre fichier.
Ahmad Maleki

Réponses:

28

"..." stream.php "reste-t-il ouvert tant que le client y est" connecté "?"

Oui, et votre pseudo-code est une approche raisonnable.

"Et comment savoir quand vous pouvez METTRE FIN à une instance de stream.php?"

Dans le cas le plus courant, cela se produit lorsque l'utilisateur quitte votre site. (Apache reconnaît le socket fermé et tue l'instance PHP.) La principale fois où vous pouvez fermer le socket du côté serveur est si vous savez qu'il n'y aura pas de données pendant un certain temps; le dernier message que vous envoyez au client est de lui dire de revenir à une certaine heure. Par exemple, dans votre cas de flux de stock, vous pouvez fermer la connexion à 20 heures et dire aux clients de revenir dans 8 heures (en supposant que le NASDAQ soit ouvert aux cotations de 4 heures à 20 heures). Vendredi soir, vous leur dites de revenir lundi matin. (J'ai un livre à venir sur l'ESS, et je dédie quelques sections à ce sujet.)

"... si tel est le cas, PHP n'est pas une technologie appropriée pour ce type de serveur. Mais toutes les démos que j'ai vues jusqu'à présent impliquent que PHP est très bien pour cela, c'est pourquoi je suis si confus..."

Eh bien, les gens affirment que PHP n'est pas une technologie appropriée pour les sites Web normaux, et ils ont raison: vous pourriez le faire avec beaucoup moins de mémoire et de cycles de processeur si vous remplaçiez toute votre pile LAMP par C ++. Cependant, malgré cela, PHP alimente très bien la plupart des sites. C'est un langage très productif pour le travail Web, en raison d'une combinaison d'une syntaxe familière de type C et de tant de bibliothèques, et réconfortant pour les gestionnaires car de nombreux programmeurs PHP à embaucher, de nombreux livres et autres ressources, et quelques gros cas d'utilisation (par exemple Facebook et Wikipédia). Ce sont essentiellement les mêmes raisons pour lesquelles vous pourriez choisir PHP comme technologie de streaming.

La configuration typique ne sera pas une connexion au NASDAQ par instance PHP. Au lieu de cela, vous allez avoir un autre processus avec une seule connexion au NASDAQ, ou peut-être une seule connexion de chaque machine de votre cluster au NASDAQ. Cela pousse ensuite les prix dans un serveur SQL / NoSQL ou dans la mémoire partagée. Ensuite, PHP interroge simplement cette mémoire partagée (ou base de données) et expulse les données. Ou, ayez un serveur de collecte de données, et chaque instance PHP ouvre une connexion de socket à ce serveur. Le serveur de collecte de données envoie des mises à jour à chacun de ses clients PHP, au fur et à mesure qu'il les reçoit, et à leur tour, ils transmettent ces données à leur client.

Le principal problème d'évolutivité lié à l'utilisation d'Apache + PHP pour le streaming est la mémoire de chaque processus Apache. Lorsque vous atteignez la limite de mémoire du matériel, prenez la décision commerciale d'ajouter une autre machine au cluster, ou supprimez Apache de la boucle et écrivez un serveur HTTP dédié. Ce dernier peut être fait en PHP afin que toutes vos connaissances et votre code existants puissent être réutilisés, ou vous pouvez réécrire toute l'application dans une autre langue. Le développeur pur en moi écrirait un serveur HTTP dédié et rationalisé en C ++. Le directeur en moi ajouterait une autre case.

Darren Cook
la source
Comme chaque processus de connexion Apache consomme de la mémoire, sera-t-il préférable d'utiliser Nginx à la place?
Zhang Buzz
@ZhangBuzz Là où j'ai dit Apache + PHP, cela signifie vraiment "serveur Web + processus PHP", donc fondamentalement aucune différence avec l'utilisation d'un serveur Web différent.
Darren Cook
Peut-être des serveurs comme ceux-ci? github.com/hoaproject/Eventsource ou github.com/hhxsv5/php-sse
Enrique
Notez également que Nginx pourrait être beaucoup plus efficace pour cela car utiliser moins de mémoire: blog.webfaction.com/2008/12
Enrique
31

Les événements envoyés par le serveur sont destinés à la mise à jour en temps réel du côté serveur vers le côté client. Dans le premier exemple, la connexion du serveur n'est pas conservée et le client essaie de se connecter à nouveau toutes les 3 secondes et fait que les événements envoyés par le serveur n'ont aucune différence avec l'interrogation ajax.

Donc, pour que la connexion persiste, vous devez envelopper votre code dans une boucle et vérifier constamment les mises à jour.

PHP est basé sur les threads et un plus grand nombre d'utilisateurs connectés rendra le serveur à court de ressources. Cela peut être résolu en contrôlant le temps d'exécution du script et en mettant fin au script lorsqu'il dépasse un certain temps (c'est-à-dire 10 minutes). leEventSource API se reconnectera automatiquement afin que le délai soit dans une plage acceptable.

Consultez également ma bibliothèque PHP pour les événements envoyés par le serveur , vous pouvez en savoir plus sur la façon de créer des événements envoyés par le serveur en PHP et de faciliter le codage.

Licson
la source
5
Pourriez-vous élaborer sur "Ce problème peut être résolu en contrôlant le temps d'exécution du script et en terminant le script lorsqu'il dépasse un certain temps"? Si vous avez un nombre excessif d'utilisateurs, cela améliorerait-il vraiment beaucoup l'utilisation des ressources en fermant la connexion puisque l'utilisateur se reconnecterait simplement dans 3 secondes?
Luke
4

J'ai remarqué que le techink sse envoie chaque couple de données de retard au client (quelque chose comme inverser le techink de regroupement des données de la page client ex Ajax pooling data.) Donc pour surmonter ce problème, j'ai fait ceci sur une page sseServer.php:

<?php
        session_start();
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache'); // recommended to prevent caching of event data
        require 'sse.php';
        if ($_POST['message'] != ""){
                $_SESSION['message'] = $_POST['message'];
                $_SESSION['serverTime'] = time();
        }
        sendMsg($_SESSION['serverTime'], $_SESSION['message'] );
?>

et le sse.php est:

<?php
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
?>

Notez que sur sseSerer.php, je démarre une session et j'utilise une variable de session! pour surmonter le problème.

J'appelle également le sseServer.php via Ajax (publication et valeur définie sur variable message ) chaque fois que je veux "mettre à jour" le message.

Maintenant, au jQuery (javascript) je fais quelque chose comme ça: 1er) je déclare une variable globale var timeStamp = 0; 2ème) j'utilise l'algorithme suivant:

if(typeof(EventSource)!=="undefined"){
        var source=new EventSource("sseServer.php");
        source.onmessage=function(event)
        if ((timeStamp!=event.lastEventId) && (timeStamp!=0)){
                /* this is initialization */
                timeStamp=event.lastEventId;
                $.notify("Please refresh "+event.data, "info");
        } else {
                if (timeStamp==0){
                         timeStamp=event.lastEventId;
                }
        } /* fi */

} else {
        document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
} /* fi */

À la ligne de: $.notify("Please refresh "+event.data, "info"); est-ce que vous pouvez gérer le message.

Dans mon cas, j'avais l'habitude d'envoyer une notification jQuery.

Vous pouvez utiliser POSIX PIPES ou une DB Table à la place pour passer le "message" via POST puisque le sseServer.php fait quelque chose comme une "boucle infinie".

Mon problème à l'époque est que le code ci-dessus N'ENVOYE PAS LE "message" à tous les clients mais uniquement à la paire (le client qui a appelé le sseServer.php fonctionne individuellement pour chaque paire) donc je vais changer le technik et en un Mise à jour de la base de données à partir de la page que je veux déclencher le "message", puis le sseServer.php à la place pour obtenir le message via POST, il l'obtiendra à partir de la table de la base de données.

J'espère avoir de l'aide!

c.chasapis
la source
3

C'est vraiment une question structurelle concernant votre application. Les événements en temps réel sont quelque chose auquel vous voulez penser dès le début, afin que vous puissiez concevoir votre application autour de cela. Si vous avez écrit une application qui exécute simplement un tas de mysql(i)_queryméthodes aléatoires en utilisant des requêtes de chaîne et ne les passe par aucune sorte d'intermédiaire, alors vous n'aurez souvent pas d'autre choix que de réécrire une grande partie de votre application, ou de faire interrogation constante côté serveur.

Si, cependant, vous gérez vos entités en tant qu'objets et les passez par une sorte de classe intermédiaire, vous pouvez vous connecter à ce processus. Regardez cet exemple:

<?php
class MyQueryManager {
    public function find($myObject, $objectId) {
        // Issue a select query against the database to get this object
    }

    public function save($myObject) {
        // Issue a query that saves the object to the database
        // Fire a new "save" event for the type of object passed to this method
    }

    public function delete($myObject) {
        // Fire a "delete" event for the type of object
    }
}

Dans votre application, lorsque vous êtes prêt à enregistrer:

<?php
$someObject = $queryManager->find("MyObjectName", 1);
$someObject->setDateTimeUpdated(time());
$queryManager->save($someObject);

Ce n'est pas l'exemple le plus gracieux, mais il devrait servir de pierre angulaire décente. Vous pouvez vous connecter à votre couche de persistance réelle pour gérer le déclenchement de ces événements. Ensuite, vous les obtenez immédiatement (aussi en temps réel que possible) sans marteler votre serveur (puisque vous n'avez pas besoin d'interroger constamment votre base de données et de voir si les choses ont changé).

Vous n'attraperez évidemment pas les modifications manuelles de la base de données de cette façon - mais si vous faites quelque chose manuellement dans votre base de données avec une fréquence quelconque, vous devriez soit:

  • Résolvez le problème qui vous oblige à effectuer une modification manuelle
  • Créez un outil pour accélérer le processus et déclencher ces événements
Colin M
la source
3
Colin - merci pour votre réponse. Ma faute - ma question n'est pas claire - mais ce n'est pas vraiment ce que je demande. Ce que je voulais demander, c'est ceci ... Si vous utilisez PHP comme "serveur" - le script PHP que vous appelez depuis EventSource dans votre client doit-il s'exécuter pendant tout le temps que le client y est connecté? Cela voudrait-il dire que si vous avez 1 000 utilisateurs simultanés, vous avez 1 000 threads distincts exécutant 1 000 instances simultanées de votre script PHP? Est-ce faisable? Et, comment sauriez-vous quand terminer le script php (en supposant qu'il est en boucle pour rester "vivant")?
mattstuehler
-7

Fondamentalement, PHP n'est pas une technologie appropriée pour ce genre de choses. Oui, vous pouvez le faire fonctionner, mais ce sera un désastre en cas de charge élevée. Nous gérons des stockservers qui envoient des signaux de changement de stock via des websockets à des dizaines de milliers d'utilisateurs - et si nous utilisions php pour cela ... Eh bien, nous pourrions, mais ces cycles maison - n'est qu'un cauchemar. Chaque connexion unique effectuera un processus distinct sur le serveur ou vous devrez gérer les connexions à partir d'une sorte de base de données.

Utilisez simplement nodejs et socket.io. Il vous permettra de démarrer facilement et d'avoir un serveur en cours d'exécution dans quelques jours. Nodejs a également ses propres limites, mais pour les connexions Websockets (et SSE), c'est maintenant la technologie la plus puissante.

Et aussi - SSE n'est pas aussi bon qu'il y paraît. Le seul avantage des websockets est que les paquets sont gzippés nativement (ws n'est pas gzippé), mais l'inconvénient est que SSE est une connexion unilatérale. Votre utilisateur, s'il veut ajouter un autre symbole boursier à l'indice, devra faire une demande ajax (y compris tous les problèmes avec le contrôle d'origine et la demande sera lente). Dans les websockets, le client et le serveur communiquent dans les deux sens en une seule connexion ouverte, donc si l'utilisateur envoie un signal de trading ou s'abonne à un devis, il envoie simplement une chaîne dans une connexion déjà ouverte. Et c'est rapide.

Trader Prosto
la source
2
Vous pouvez utiliser React.php de la même manière que la boucle d'événement dans node.js.
Matěj Koubík
3
Bien qu'il soit bon de dire que PHP n'est pas le meilleur choix, je pense que vous devriez au moins inclure quelque chose que OP a demandé.
Nishchal Gautam