Сервер-отправленные события и php - что вызывает события на сервере?

Все,

HTML5 У Rocks есть приятное начинающее учебное пособие по событиям, отправленным сервером (SSE):

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

Но я не понимаю важной концепции - что вызывает событие на сервере, которое вызывает отправку сообщения?

Другими словами - в примере HTML5 сервер просто отправляет временную метку один раз:

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

Если бы я строил практический пример - например, "стену" в стиле Facebook или биржевой тиккер, в котором сервер "нажимал" новое сообщение клиенту каждый раз, когда какая-то часть данных изменяется, как которые работают?

Другими словами... Имеет ли PHP скрипт цикл, который работает непрерывно, проверяя изменение данных, а затем отправляя сообщение каждый раз, когда он находит его? Если да - как вы знаете, когда нужно завершить этот процесс?

Или - просто PHP script просто отправляет сообщение, а затем заканчивает (как это выглядит в примере HTML5Rocks)? Если да - как вы получаете непрерывные обновления? Является ли браузер простым опросом PHP-страницы через регулярные промежутки времени? Если да - как это "событие, отправленное сервером"? Как это отличается от написания функции setInterval в JavaScript, которая использует AJAX для вызова страницы PHP с регулярным интервалом?

Извините - это, наверное, невероятно наивный вопрос. Но ни один из примеров, которые я смог найти, не делает этого ясно.

[ОБНОВЛЕНИЕ]

Я думаю, что мой вопрос был слабо сформулирован, поэтому здесь некоторые разъяснения.

Скажем, у меня есть веб-страница, на которой должна отображаться самая последняя цена акций Apple.

Когда пользователь сначала открывает страницу, страница создает EventSource с URL-адресом моего "потока".

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

Мой вопрос в том, как работать "stream.php"?

Как это? (Псевдо-код):

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

Другими словами, "stream.php" остается открытым, пока клиент "подключен" к нему?

Если это так - означает ли это, что у вас столько потоков, работающих под управлением stream.php, как у вас есть одновременные пользователи? Если это так - возможно, это возможно, или подходящий способ создания приложения? И откуда вы знаете, когда вы можете END экземпляр stream.php?

Мое наивное впечатление заключается в том, что если это так, PHP не подходит для такого типа сервера. Но все демонстрации, которые я видел до сих пор, подразумевают, что PHP для этого просто подходит, поэтому я так запутался...

Спасибо заранее.

Ответ 1

События, отправленные сервером, предназначены для обновления в реальном времени с серверной стороны на стороне клиента. В первом примере соединение с сервером не сохраняется, и клиент пытается подключиться снова каждые 3 секунды и делает события, отправленные сервером, без разницы в ajax-опросе.

Итак, чтобы сохранить соединение, вам необходимо обернуть свой код в цикле и постоянно проверять наличие обновлений.

PHP основан на потоках, и более подключенные пользователи заставят сервер работать без ресурсов. Это можно решить, контролируя время выполнения script и заканчивая script, когда оно превышает количество времени (т.е. 10 минут). API EventSource автоматически подключится снова, чтобы задержка была в допустимом диапазоне.

Кроме того, ознакомьтесь с моей библиотекой PHP для событий, отправленных сервером, вы можете узнать больше о том, как делать серверные события на PHP и упрощать код.

Ответ 2

"..." stream.php "остается открытым до тех пор, пока клиент" подключен "к нему?"

Да, и ваш псевдокод - разумный подход.

"И откуда вы знаете, когда вы можете END экземпляр stream.php?"

В наиболее типичном случае это происходит, когда пользователь покидает ваш сайт. (Apache распознает закрытый сокет и убивает экземпляр PHP.) Основной момент, когда вы можете закрыть сокет с серверной стороны, - это если вы знаете, что на какое-то время не будет данных; последнее сообщение, которое вы отправляете клиенту, - это сказать им вернуться в определенное время. Например. в вашем потоке с запасом вы можете закрыть соединение в 8 вечера и сообщить клиентам вернуться через 8 часов (при условии, что NASDAQ открыт для котировок с 4:00 до 20:00). В пятницу вечером вы скажите им вернуться в понедельник утром. (У меня есть предстоящая книга по SSE, и посвятите пару разделов по этому вопросу.)

"... если это так, PHP не подходит для такого рода технологий сервера. Но все демо, которые я видел до сих пор, подразумевают, что PHP просто отлично для этого, вот почему я так запутался..."

Ну, люди утверждают, что PHP не является подходящей технологией для обычных веб-сайтов, и они правы: вы могли бы сделать это с гораздо меньшим количеством циклов памяти и процессора, если бы вы заменили весь стек LAMP на С++. Однако, несмотря на это, PHP предоставляет большинство сайтов там просто отлично. Это очень продуктивный язык для веб-работы, благодаря сочетанию знакомого синтаксиса C-like и так много библиотек, а также удобному для менеджеров, поскольку многие программисты PHP нанимают, много книг и других ресурсов, а также некоторые крупные (например, Facebook и Википедия). В основном это те же причины, по которым вы можете выбрать PHP как свою технологию потоковой передачи.

Типичная настройка не будет одним подключением к NASDAQ для PHP-экземпляра. Вместо этого у вас будет другой процесс с единственным подключением к NASDAQ или, возможно, с одним соединением с каждого компьютера в вашем кластере на NASDAQ. Это затем подталкивает цены на сервер SQL/NoSQL или в общую память. Затем PHP просто проверяет эту общую память (или базу данных) и выталкивает данные. Или, у вас есть сервер сбора данных, и каждый экземпляр PHP открывает сокет-соединение с этим сервером. Сервер сбора данных выталкивает обновления для каждого из своих PHP-клиентов, поскольку он их получает, и они, в свою очередь, выталкивают эти данные своему клиенту.

Основная проблема масштабируемости при использовании Apache + PHP для потоковой передачи - это память для каждого процесса Apache. Когда вы достигнете предела памяти аппаратного обеспечения, сделайте бизнес-решение о добавлении другого компьютера в кластер или вырезайте Apache из цикла и напишите выделенный HTTP-сервер. Последнее можно сделать на PHP, чтобы все ваши существующие знания и код можно было повторно использовать, или вы можете переписать все приложение на другом языке. Чистый разработчик во мне напишет выделенный, оптимизированный HTTP-сервер в С++. Менеджер во мне добавил бы еще одну коробку.

Ответ 3

Я заметил, что sse techink отправляет клиенту каждую пару данных задержки (что-то вроде того, что реверсирует сбор данных данных из клиентской страницы ex Ajax). Чтобы преодолеть эту проблему, я сделал это на странице 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'] );
?>

а sse.php:

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

Обратите внимание, что на sseSerer.php я запускаю сеанс и используя переменную сеанса! для преодоления проблемы.

Также я вызываю sseServer.php через Ajax (размещение и заданное значение variable message) каждый раз, когда я хочу "обновить" сообщение.

Теперь в jQuery (javascript) я делаю что-то вроде этого: 1-й) я объявляет глобальную переменную var timeStamp = 0; 2nd) я использую следующий алгоритм:

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

В строке: $.notify("Please refresh "+event.data, "info"); что вы можете обрабатывать сообщение.

В моем случае я использовал для отправки уведомления jQuery.

Вместо этого вы можете использовать POSIX PIPES или таблицу DB для передачи "сообщения" через POST, поскольку sseServer.php делает что-то вроде "бесконечного цикла".

Моя проблема в то время заключается в том, что приведенный выше код НЕ СЕНДЕТ "сообщение" всем клиентам, а только к паре (клиент, который вызвал sseServer.php, работает как индивидуальный для каждой пары), поэтому я изменю техник и к обновлению БД со страницы, которую я хочу вызвать "сообщение", а затем sseServer.php вместо этого, чтобы получить сообщение через POST, он получит его из таблицы БД.

Я надеюсь, что у меня будет помощь!

Ответ 4

Это действительно структурный вопрос о вашем приложении. События в реальном времени - это то, о чем вы хотите подумать с самого начала, чтобы вы могли проектировать свое приложение вокруг него. Если вы написали приложение, которое просто запускает кучу случайных методов mysql(i)_query, используя строковые запросы, и не передает их через какой-либо посредник, то много раз у вас не будет выбора, кроме как переписать большую часть вашего приложения, или выполнять постоянный опрос на серверной стороне.

Если, однако, вы управляете своими объектами как объектами и передаете их через какой-то промежуточный класс, вы можете подключиться к этому процессу. Посмотрите на этот пример:

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

В вашем приложении, когда вы будете готовы к сохранению:

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

Это не самый изящный пример, но он должен служить достойным строительным блоком. Вы можете подключиться к вашему фактическому уровню сохранения, чтобы обрабатывать эти события. Затем вы получаете их сразу (как в режиме реального времени, как может получить), не забивая ваш сервер (так как вам не нужно постоянно запрашивать вашу базу данных и видеть, изменились ли какие-либо вещи).

Очевидно, что вы не пойдете на ручные изменения в базе данных, но если вы делаете что-либо вручную в своей базе данных с любой частотой, вы должны либо:

  • Устранить проблему, требующую изменения вручную.
  • Создайте инструмент для ускорения процесса и выполните следующие действия.

Ответ 5

В принципе, PHP не подходит технологии для такого рода вещей. Да, вы можете заставить его работать, но это будет катастрофа на высокой нагрузке. Мы запускаем агенты-хранилища, которые отправляют сигналы изменения запасов через веб-порты до десятков тысяч пользователей - и если мы будем использовать php для этого... Ну, мы могли бы, но эти домашние циклы - это просто кошмар. Каждое отдельное соединение будет выполнять отдельный процесс на сервере или вам придется обрабатывать соединения из какой-либо базы данных.

Просто используйте nodejs и socket.io. Это позволит вам легко запустить и запустить запущенный сервер через пару дней. У Nodejs также есть свои ограничения, но для соединений websockets (и SSE) теперь является самой мощной технологией.

А также - SSE не так хорош, как кажется. Единственное преимущество для websockets - это то, что пакеты gzipped изначально (ws не gzipped), но с другой стороны, SSE является односторонним соединением. Пользователь, если он хочет добавить еще один символ запаса в подкодер, должен будет сделать запрос ajax (включая все проблемы с контролем происхождения и запрос будет медленным). В websockets клиент и сервер обмениваются обоими путями в одном открытом открывшемся соединении, поэтому, если пользователь отправляет торговый сигнал или подписывается на котировку, он просто отправляет строку в уже открытое соединение. И это быстро.