Zdarzenia wysyłane przez serwer i php - co wyzwala zdarzenia na serwerze?


93

Wszystko,

HTML5 Rocks ma fajny samouczek dla początkujących na temat zdarzeń wysyłanych przez serwer (SSE):

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

Ale nie rozumiem ważnej koncepcji - co wyzwala zdarzenie na serwerze, które powoduje wysłanie wiadomości?

Innymi słowy - w przykładzie HTML5 - serwer po prostu wysyła raz znacznik czasu :

<?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()));

Gdybym budował praktyczny przykład - np. „Ścianę” w stylu Facebooka lub giełdę, w której serwer „wysyłałby” nową wiadomość do klienta za każdym razem, gdy zmieniają się dane, jak to działa?

Innymi słowy ... Czy skrypt PHP ma pętlę, która działa w sposób ciągły, sprawdzając zmiany w danych, a następnie wysyłając wiadomość za każdym razem, gdy ją znajdzie? Jeśli tak - skąd wiesz, kiedy zakończyć ten proces?

Lub - czy skrypt PHP po prostu wysyła wiadomość, a następnie kończy (jak wygląda to w przykładzie z HTML5Rocks)? Jeśli tak - w jaki sposób otrzymujesz ciągłe aktualizacje? Czy przeglądarka po prostu odpytuje stronę PHP w regularnych odstępach czasu? Jeśli tak - w jaki sposób to „zdarzenie wysłane przez serwer”? Czym różni się to od pisania funkcji setInterval w JavaScript, która używa AJAX do wywoływania strony PHP w regularnych odstępach czasu?

Przepraszam - to prawdopodobnie niesamowicie naiwne pytanie. Ale żaden z przykładów, które udało mi się znaleźć, nie wyjaśnia tego jasno.

[AKTUALIZACJA]

Myślę, że moje pytanie było źle sformułowane, więc oto kilka wyjaśnień.

Załóżmy, że mam stronę internetową, na której powinna być wyświetlana najnowsza cena akcji Apple.

Gdy użytkownik po raz pierwszy otwiera stronę, strona tworzy EventSource z adresem URL mojego „strumienia”.

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

Moje pytanie brzmi: jak powinien działać plik „stream.php”?

Lubię to? (pseudo kod):

<?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
    }
?>

Innymi słowy - czy „stream.php” pozostaje otwarty tak długo, jak długo klient jest z nim „połączony”?

Jeśli tak - czy to oznacza, że ​​masz uruchomionych tyle wątków, stream.phpile masz jednocześnie użytkowników? Jeśli tak - czy jest to wykonalne zdalnie, czy też jest to właściwy sposób tworzenia aplikacji? A skąd wiesz, kiedy możesz ZAKOŃCZYĆ wystąpienie stream.php?

Mam naiwne wrażenie, że jeśli tak jest, PHP nie jest odpowiednią technologią dla tego rodzaju serwera. Ale wszystkie dema, które widziałem do tej pory, sugerują, że PHP jest do tego w porządku, dlatego jestem tak zdezorientowany ...


To część, którą programista musi samodzielnie kodować. Sposoby uzyskiwania danych są za pośrednictwem gniazd sieciowych / długiego odpytywania itp. Jednak sztuczka polega na tym, co wyzwala zdarzenie. Osobiście eksperymentowałem z kilkoma podejściami, a jedno podejście, które mi się podobało (ale nie było tak bezpieczne), polegało na tym, że MySQL wyzwalał program konsolowy za każdym razem, gdy coś zostało wstawione do określonej tabeli. Program konsoli otrzymałby informację o zmienionym / wstawionym rekordzie i wysłałby powiadomienie do odpowiedniego użytkownika poprzez WebSockets. Zasadniczo miałem demona PHP czekającego na wysyłanie wiadomości.
NB

Jeden problem z tym, SSE nie jest obsługiwane przez IE: - / Również przeczytałbym to prodigyproductionsllc.com/articles/programming/javascript/ ... Myślę, że używa portu, aby uniknąć problemu zbyt wielu dzieci, ale ogólnie wygląda jak jego Zaleca się unikanie SSE. Wygląda na to, że więcej kłopotów niż to warte, IMO.
PJ Brunet

Obecnie nie jest obsługiwany przez przeglądarkę IE11 ani przeglądarkę Android caniuse.com/eventsource
PJ Brunet


4
Miałem to samo pytanie i myślę, że dogłębnie rozumiem, co masz na myśli mówiąc o tym, co wyzwala zdarzenie na serwerze… . Kiedy tworzysz obiekt programu EventSource('stream.php'), klient otwiera połączenie, stream.phpktóre przypomina wywołanie go przez ajax. TO połączenie uruchamia kod po stronie serwera i utrzymuje połączenie otwarte, o ile kod po stronie serwera ma coś do powiedzenia. Następnie połączenie zostaje zamknięte i po krótkim opóźnieniu (myślę, że 3 sekundy w chrome) klient ponownie otwiera połączenie, które stream.phpponownie uruchamia plik.
Ahmad Maleki,

Odpowiedzi:


29

„… czy„ stream.php ”pozostaje otwarty tak długo, jak klient jest z nim„ połączony ”?

Tak, a twój pseudokod to rozsądne podejście.

„A skąd wiesz, kiedy możesz ZAKOŃCZYĆ instancję stream.php?”

W najbardziej typowym przypadku dzieje się tak, gdy użytkownik opuszcza witrynę. (Apache rozpoznaje zamknięte gniazdo i zabija instancję PHP.) Główny czas, w którym możesz zamknąć gniazdo po stronie serwera, to wtedy, gdy wiesz, że przez chwilę nie będzie żadnych danych; ostatnia wiadomość, jaką wysyłasz klientowi, to nakazanie mu powrotu o określonej godzinie. Na przykład w przypadku przesyłania strumieniowego akcji możesz zamknąć połączenie o 20:00 i powiedzieć klientom, aby wrócili za 8 godzin (zakładając, że NASDAQ jest otwarty na wyceny od 4:00 do 20:00). W piątek wieczorem każesz im wrócić w poniedziałek rano. (Mam nadchodzącą książkę o SSE i poświęcę kilka sekcji na ten temat.)

"... jeśli tak jest, PHP nie jest odpowiednią technologią dla tego rodzaju serwera. Jednak wszystkie dema, które widziałem do tej pory, sugerują, że PHP jest do tego odpowiednie, dlatego jestem taki zmieszany..."

Cóż, ludzie twierdzą, że PHP nie jest odpowiednią technologią dla normalnych witryn internetowych, i mają rację: można to zrobić przy znacznie mniejszej ilości pamięci i cykli procesora, gdyby zastąpić cały stos LAMP C ++. Jednak mimo to PHP obsługuje większość witryn. Jest to bardzo produktywny język do pracy w sieci, ze względu na połączenie znanej składni podobnej do języka C i tak wielu bibliotek, a także wygodny dla menedżerów, jak wielu programistów PHP do wynajęcia, mnóstwo książek i innych zasobów oraz kilka dużych przypadki użycia (np. Facebook i Wikipedia). Są to w zasadzie te same powody, dla których możesz wybrać PHP jako technologię przesyłania strumieniowego.

Typowa konfiguracja nie będzie obejmowała jednego połączenia z NASDAQ na instancję PHP. Zamiast tego będziesz mieć inny proces z pojedynczym połączeniem z NASDAQ lub być może jednym połączeniem z każdej maszyny w klastrze do NASDAQ. To następnie wypycha ceny do serwera SQL / NoSQL lub do pamięci współdzielonej. Następnie PHP po prostu sonduje tę pamięć współdzieloną (lub bazę danych) i wypycha dane. Możesz też mieć serwer gromadzący dane, a każda instancja PHP otwiera połączenie przez gniazdo z tym serwerem. Serwer gromadzący dane wysyła aktualizacje do każdego ze swoich klientów PHP, gdy je otrzymuje, a oni z kolei wysyłają te dane do swojego klienta.

Głównym problemem związanym ze skalowalnością przy używaniu Apache + PHP do przesyłania strumieniowego jest pamięć dla każdego procesu Apache. Po osiągnięciu limitu pamięci sprzętu podejmij decyzję biznesową o dodaniu kolejnej maszyny do klastra lub wyłącz Apache z pętli i napisz dedykowany serwer HTTP. To ostatnie można zrobić w PHP, dzięki czemu cała istniejąca wiedza i kod mogą zostać ponownie wykorzystane lub możesz przepisać całą aplikację w innym języku. Czysty programista we mnie napisałby dedykowany, usprawniony serwer HTTP w C ++. Menedżer we mnie dodałby kolejne pudełko.


Ponieważ każdy proces połączenia Apache zużywa pamięć, czy lepiej będzie zamiast tego użyć Nginx?
Zhang Buzz,

@ZhangBuzz Tam, gdzie powiedziałem Apache + PHP, to naprawdę oznacza „serwer WWW + proces PHP”, więc w zasadzie nie ma różnicy w używaniu innego serwera WWW.
Darren Cook,


Zwróć również uwagę, że Nginx może być znacznie bardziej wydajny, ponieważ zużywa
Enrique

32

Zdarzenia wysyłane przez serwer służą do aktualizacji w czasie rzeczywistym od strony serwera do klienta. W pierwszym przykładzie połączenie z serwera nie jest utrzymywane, a klient próbuje połączyć się ponownie co 3 sekundy i sprawia, że ​​zdarzenia wysyłane przez serwer nie różnią się od odpytywania ajax.

Tak więc, aby utrzymać połączenie, musisz zawinąć kod w pętlę i stale sprawdzać dostępność aktualizacji.

PHP jest oparte na wątkach i więcej podłączonych użytkowników spowoduje, że na serwerze zabraknie zasobów. Można to rozwiązać kontrolując czas wykonywania skryptu i kończąc skrypt, gdy przekroczy on określony czas (np. 10 minut). EventSourceAPI automatycznie ponownie połączyć tak opóźnienie w dopuszczalnym zakresie.

Sprawdź również moją bibliotekę PHP pod kątem zdarzeń wysyłanych przez serwer , możesz dowiedzieć się więcej o tym, jak wykonywać zdarzenia wysyłane z serwera w PHP i ułatwić kodowanie.


5
Czy możesz rozwinąć temat „Można to rozwiązać, kontrolując czas wykonywania skryptu i kończąc skrypt, gdy przekroczy on określony czas”? Jeśli masz nadmierną liczbę użytkowników, czy naprawdę poprawi to wykorzystanie zasobów poprzez zamknięcie połączenia, ponieważ użytkownik połączy się ponownie za 3 sekundy?
Łukasz

4

Zauważyłem, że sse techink wysyła co kilka opóźnień do klienta (coś w rodzaju odwrócenia łącza technicznego puli danych ze strony klienta ex Ajax pulowania danych). Aby rozwiązać ten problem, zrobiłem to na stronie 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'] );
?>

a sse.php to:

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

Zwróć uwagę, że w sseSerer.php uruchamiam sesję i używam zmiennej sesji! aby przezwyciężyć problem.

Wywołuję również sseServer.php przez Ajax (wysyłanie i ustawianie wartości variable message) za każdym razem, gdy chcę zaktualizować wiadomość.

Teraz w jQuery (javascript) robię coś takiego: 1.) deklaruję zmienną globalną var timeStamp = 0; 2nd) używam następnego algorytmu:

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 */

W wierszu: $.notify("Please refresh "+event.data, "info"); czy możesz obsłużyć wiadomość.

W moim przypadku wysyłałem powiadomienie jQuery.

Możesz zamiast tego użyć POSIX PIPES lub tabeli DB, aby przekazać "wiadomość" przez POST, ponieważ sseServer.php robi coś w rodzaju "nieskończonej pętli".

Mój problem w tym czasie polega na tym, że powyższy kod NIE WYSYŁA "wiadomości" do wszystkich klientów, ale tylko do pary (klient, który wywołał sseServer.php, działa indywidualnie dla każdej pary), więc zmienię technika i Aktualizacja bazy danych ze strony, na której chcę wyzwolić "wiadomość", a następnie sseServer.php zamiast tego, aby uzyskać wiadomość przez POST, otrzyma ją z tabeli DB.

Mam nadzieję, że mam pomoc!


3

To jest naprawdę strukturalne pytanie dotyczące twojej aplikacji. Zdarzenia w czasie rzeczywistym to coś, o czym chcesz myśleć od samego początku, abyś mógł wokół nich zaprojektować swoją aplikację. Jeśli napisałeś aplikację, która po prostu uruchamia kilka losowych mysql(i)_querymetod przy użyciu zapytań łańcuchowych i nie przekazuje ich przez żaden rodzaj pośrednika, to wiele razy nie będziesz miał wyboru i musisz albo przepisać znaczną część aplikacji, albo zrobić stałe sondowanie po stronie serwera.

Jeśli jednak zarządzasz swoimi bytami jak obiektami i przepuszczasz je przez jakąś klasę pośredniczącą, możesz podłączyć się do tego procesu. Spójrz na ten przykład:

<?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
    }
}

W swojej aplikacji, gdy jesteś gotowy do zapisania:

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

Nie jest to najwspanialszy przykład, ale powinien służyć jako porządny element konstrukcyjny. Możesz podłączyć się do rzeczywistej warstwy trwałości, aby obsłużyć wyzwalanie tych zdarzeń. Następnie otrzymujesz je natychmiast (w czasie rzeczywistym, jak to tylko możliwe) bez wbijania serwera (ponieważ nie musisz ciągle wysyłać zapytań do bazy danych i sprawdzać, czy coś się zmieniło).

Oczywiście nie wyłapiesz w ten sposób ręcznych zmian w bazie danych - ale jeśli robisz coś ręcznie w bazie danych z jakąkolwiek częstotliwością, powinieneś:

  • Rozwiąż problem, który wymaga ręcznej zmiany
  • Zbuduj narzędzie przyspieszające ten proces i odpalaj te zdarzenia

3
Colin - dzięki za odpowiedź. Moja wina - moje pytanie nie jest jasne - ale tak naprawdę nie o to proszę. Chodziło mi o to, aby zapytać ... Jeśli używasz PHP jako swojego „serwera” - czy skrypt PHP, który wywołujesz ze źródła zdarzeń w swoim kliencie, musi działać przez cały czas, gdy klient jest z nim połączony? Czy oznaczałoby to, że jeśli masz 1000 jednoczesnych użytkowników, masz 1000 oddzielnych wątków z 1000 jednoczesnych wystąpień skryptu PHP? Czy to możliwe? A skąd możesz wiedzieć, kiedy zakończyć skrypt php (zakładając, że zapętla się, aby pozostać „żywy”)?
mattstuehler

-7

Zasadniczo PHP nie jest odpowiednią technologią do tego typu rzeczy. Tak, możesz sprawić, że to zadziała, ale przy dużym obciążeniu będzie to katastrofa. Prowadzimy serwery giełdowe, które wysyłają sygnały o zmianie zapasów za pośrednictwem gniazd internetowych do dziesiątek tysięcy użytkowników - a gdybyśmy użyli do tego php ... Cóż, moglibyśmy, ale te domowe cykle - to tylko koszmar. Każde połączenie spowoduje oddzielny proces na serwerze lub będziesz musiał obsługiwać połączenia z jakiejś bazy danych.

Po prostu użyj nodejs i socket.io. Pozwoli ci to łatwo uruchomić i mieć działający serwer w ciągu kilku dni. Nodejs ma również swoje ograniczenia, ale w przypadku połączeń sieciowych (i SSE) jest to obecnie najpotężniejsza technologia.

A także - SSE nie jest takie dobre, jak się wydaje. Jedyną zaletą websockets jest to, że pakiety są natywne gzipowane (ws nie jest gzipowane), ale wadą jest to, że SSE jest połączeniem jednostronnym. Jeśli użytkownik chce dodać kolejny symbol giełdowy do subscripton, będzie musiał wysłać żądanie Ajax (w tym wszystkie problemy z kontrolą pochodzenia, a żądanie będzie wolne). W sieciach klient i serwer komunikują się w obie strony w jednym otwartym połączeniu, więc jeśli użytkownik wysyła sygnał transakcyjny lub zapisuje się do wyceny, po prostu wysyła ciąg w już otwartym połączeniu. I to szybko.


2
Możesz używać React.php zasadniczo w taki sam sposób, jak pętli zdarzeń w node.js.
Matěj Koubík,

3
Chociaż dobrze jest powiedzieć, że PHP nie jest najlepszym wyborem, myślę, że powinieneś przynajmniej dołączyć coś, o co prosił OP.
Nishchal Gautam
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.