Отправленные сервером события и 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 просто отправляет сообщение, а затем завершает работу (как это, похоже, имеет место в примере 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'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
    }
?>

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

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

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


person mattstuehler    schedule 28.01.2013    source источник
comment
Это та часть, которую разработчик должен кодировать самостоятельно. Способы получения данных - через веб-сокеты / длинный опрос и т. Д., Однако фокус в том, что что вызывает событие. Лично я экспериментировал с несколькими подходами, и один подход, который мне нравился (но он не был тем отказоустойчивым), заставлял MySQL запускать консольную программу каждый раз, когда что-то было вставлено в определенную таблицу. . Консольная программа получит информацию об измененной / вставленной записи и отправит уведомление соответствующему пользователю через WebSockets. В основном у меня был демон PHP, ожидающий отправки сообщений.   -  person N.B.    schedule 28.01.2013
comment
Одна из проблем заключается в том, что SSE не поддерживается IE: - / Также я бы прочитал это prodigyproductionsllc.com/articles/programming/javascript/ Я думаю, что он использует порт, чтобы избежать проблемы слишком большого количества детей, но в целом похоже, что его рекомендация - избегать SSE. Похоже, проблем больше, чем того стоит, ИМО.   -  person PJ Brunet    schedule 08.11.2013
comment
В настоящее время не поддерживается IE11 или браузером Android. caniuse.com/eventsource   -  person PJ Brunet    schedule 08.11.2013
comment
Я предлагаю НЕ использовать цикл while, вместо этого вы должны использовать цикл for. Поскольку время sleep () не рассчитывается в максимальном времени выполнения php, это означает, что цикл while будет работать слишком долго, прежде чем весь скрипт достигнет максимального времени выполнения, и это приведет к тому, что процесс php будет занимать вашу память надолго. время. Если ваше время сна составляет 3 секунды, я предлагаю 100 циклов for, то есть 5 минут, и через 5 минут скрипт php спокойно остановится, и клиент может запустить новый процесс php, если это необходимо.   -  person Zhang Buzz    schedule 08.07.2016
comment
Если кому-то нужен sse php-код: github.com/shahzadthathal/server- отправлено-события-php-пример   -  person Muhammad Shahzad    schedule 28.07.2016
comment
У меня был тот же вопрос, и я думаю, что хорошо понимаю, что вы имеете в виду под что вызывает событие на сервере .... Когда вы создаете объект EventSource('stream.php'), клиент открывает соединение с stream.php, что похоже на его вызов с помощью ajax. ЭТО соединение запускает ваш серверный код и сохраняет соединение открытым до тех пор, пока вашему серверному коду есть что сказать. Затем соединение закрывается, и после небольшой задержки (кажется, 3 секунды в chrome) клиент повторно открывает соединение, которое снова запускает ваш stream.php файл.   -  person Ahmad Maleki    schedule 16.07.2017
comment
Я думаю, что для веб-сайтов с высоким трафиком и PHP-бэкэндом SSE не является хорошим решением, так как на сервере закончатся ресурсы, какова ваша идея через 4 года, реализовали ли вы ее и хорошо ли она сработала?   -  person Yuseferi    schedule 13.01.2018
comment
Не могли бы вы поделиться с нами своим опытом? Вы наконец нашли хороший и исчерпывающий ответ на свои вопросы?   -  person Reza Amya    schedule 13.10.2019


Ответы (5)


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

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

«А как узнать, можно ли ЗАКОНЧИТЬ экземпляр stream.php?»

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

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

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

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

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

person Darren Cook    schedule 01.11.2013
comment
Поскольку каждый процесс подключения к Apache потребляет память, будет ли лучше использовать Nginx? - person Zhang Buzz; 08.07.2016
comment
@ZhangBuzz Где я сказал Apache + PHP, на самом деле это означает веб-сервер + процесс PHP, поэтому в принципе никакой разницы с использованием другого веб-сервера. - person Darren Cook; 08.07.2016
comment
Может, такие сервера? github.com/hoaproject/Eventsource или github.com/hhxsv5/php-sse - person Enrique; 25.04.2019
comment
Также обратите внимание, что Nginx может быть намного более эффективным для этого, поскольку использует меньше памяти: blog.webfaction.com/2008/12/ - person Enrique; 25.04.2019

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

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

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

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

person Licson    schedule 12.02.2013
comment
Не могли бы вы уточнить, что это можно решить, контролируя время выполнения скрипта и завершая скрипт, когда оно превышает время? Если у вас чрезмерное количество пользователей, действительно ли это значительно улучшит использование ресурсов, закрыв соединение, поскольку пользователь просто подключится снова через 3 секунды? - person Luke; 20.10.2014

Я заметил, что sse techink отправляет каждую пару данных о задержке клиенту (что-то вроде реверсирования пула данных techink со страницы клиента e.x. Ajax pooling data.), Поэтому, чтобы преодолеть эту проблему, я сделал это на странице 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; 2) я использую следующий алгоритм:

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 или таблицу БД вместо того, чтобы передавать «сообщение» через POST, поскольку sseServer.php выполняет что-то вроде «бесконечного цикла».

Моя проблема в то время заключается в том, что приведенный выше код НЕ ОТПРАВЛЯЕТ «сообщение» всем клиентам, а только паре (клиент, который вызвал sseServer.php, работает индивидуально для каждой пары), поэтому я изменю технику и на Обновление БД со страницы, на которой я хочу вызвать «сообщение», а затем sseServer.php вместо того, чтобы получить сообщение через POST, он получит его из таблицы БД.

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

person c.chasapis    schedule 13.02.2014

Это действительно структурный вопрос о вашем приложении. События в реальном времени - это то, о чем вы хотите думать с самого начала, чтобы вы могли проектировать свое приложение на основе этого. Если вы написали приложение, которое просто запускает кучу случайных 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);

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

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

  • Устраните проблему, требующую внесения изменений вручную
  • Создайте инструмент для ускорения процесса и запускайте эти события
person Colin M    schedule 28.01.2013
comment
Колин - спасибо за ответ. Моя вина - мой вопрос не ясен - но на самом деле я спрашиваю не об этом. Я имел в виду спросить следующее ... Если вы используете PHP в качестве сервера - должен ли PHP-скрипт, который вы вызываете из EventSource в вашем клиенте, запускать все время клиент к нему подключен? Означает ли это, что если у вас 1000 одновременных пользователей, у вас будет 1000 отдельных потоков, выполняющих 1000 одновременных экземпляров вашего PHP-скрипта? Это возможно? И как узнать, когда нужно завершить скрипт php (если он зацикливается, чтобы остаться в живых)? - person mattstuehler; 28.01.2013

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

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

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

person Prosto Trader    schedule 19.04.2015
comment
Вы можете использовать React.php в основном так же, как цикл событий в node.js. - person Matěj Koubík; 07.12.2016
comment
Хотя хорошо сказать, что PHP - не лучший выбор, я думаю, вы должны хотя бы включить то, о чем просил OP. - person ; 01.02.2017