Как реализовать прослушивание событий в PHP

Вот моя проблема: у меня есть сценарий (давайте назовем его comet.php), который требуется для клиентского сценария AJAX и жду, пока произойдет изменение, подобное этому:

while(no_changes){
    usleep(100000);
    //check for changes
}

Мне это не очень нравится, это не очень масштабируемо и (imho) "плохая практика". Я хотел бы улучшить это поведение с помощью семафора (?) Или в любом случае техники параллельного программирования. Можете ли вы дать мне несколько советов, как справиться с этим? (Я знаю, это не короткий ответ, но отправной точки будет достаточно.)

Редактировать: как насчет LibEvent?

5 ответов

Решение

Вы можете решить эту проблему, используя ZeroMQ.

ZeroMQ - это библиотека, которая предоставляет сокеты с наддувом для соединения объектов (потоков, процессов и даже отдельных машин) вместе.

Я предполагаю, что вы пытаетесь передать данные с сервера на клиент. Что ж, хороший способ сделать это - использовать API EventSource ( доступны полифилы).

client.js

Подключается к stream.php через EventSource.

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

stream.addEventListener('debug', function (event) {
    var data = JSON.parse(event.data);
    console.log([event.type, data]);
});

stream.addEventListener('message', function (event) {
    var data = JSON.parse(event.data);
    console.log([event.type, data]);
});

router.php

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

<?php

$context = new ZMQContext();

$pull = $context->getSocket(ZMQ::SOCKET_PULL);
$pull->bind("tcp://*:5555");

$pub = $context->getSocket(ZMQ::SOCKET_PUB);
$pub->bind("tcp://*:5556");

while (true) {
    $msg = $pull->recv();
    echo "publishing received message $msg\n";
    $pub->send($msg);
}

stream.php

Каждый пользователь, подключающийся к сайту, получает свой файл stream.php. Этот скрипт долго работает и ждет любых сообщений от роутера. Как только он получает новое сообщение, он выводит это сообщение в формате EventSource.

<?php

$context = new ZMQContext();

$sock = $context->getSocket(ZMQ::SOCKET_SUB);
$sock->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, "");
$sock->connect("tcp://127.0.0.1:5556");

set_time_limit(0);
ini_set('memory_limit', '512M');

header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");

while (true) {
    $msg = $sock->recv();
    $event = json_decode($msg, true);
    if (isset($event['type'])) {
        echo "event: {$event['type']}\n";
    }
    $data = json_encode($event['data']);
    echo "data: $data\n\n";
    ob_flush();
    flush();
}

Чтобы отправлять сообщения всем пользователям, просто отправьте их на маршрутизатор. Затем маршрутизатор распространит это сообщение во всех прослушиваемых потоках. Вот пример:

<?php

$context = new ZMQContext();

$sock = $context->getSocket(ZMQ::SOCKET_PUSH);
$sock->connect("tcp://127.0.0.1:5555");

$msg = json_encode(array('type' => 'debug', 'data' => array('foo', 'bar', 'baz')));
$sock->send($msg);

$msg = json_encode(array('data' => array('foo', 'bar', 'baz')));
$sock->send($msg);

Это должно доказать, что вам не нужен node.js для программирования в реальном времени. PHP может справиться с этим просто отлично.

Кроме того, socket.io - действительно хороший способ сделать это. И вы можете легко подключиться к socket.io к вашему PHP-коду через ZeroMQ.

Смотрите также

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

Однако, если вы делаете что-то, что включает в себя вызов функции, которая будет блокировать, пока что-то не произойдет, вы можете использовать это, чтобы избежать гонок вместо usleep() позвонить (что является ИМХО той частью, которая будет считаться "плохой практикой").

Скажем, вы ждали данные из файла или другого потока, который блокирует. Вы могли бы сделать это:

while (($str = fgets($fp)) === FALSE) continue;
// Handle the event here

Действительно, PHP не тот язык, чтобы делать подобные вещи. Но есть ситуации (я знаю, потому что я имел дело с ними сам), где PHP является единственным вариантом.

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

Теперь, если вы не хотите тратить впустую циклы ЦП, вам нужно создать скрипт PHP, который будет подключаться к какому-либо серверу через определенный порт. Указанный сервер должен прослушивать соединения на выбранном порту и каждые X проверять количество времени на предмет того, что вы хотите проверить (например, записи в базе данных для новых сообщений), а затем отправляет каждому подключенному клиенту сообщение о том, что новая запись готова.

Теперь не так сложно реализовать эту архитектуру очереди событий в PHP, но вам потребуется буквально 5 минут, чтобы сделать это с Node.js и Socket.IO, не беспокоясь о том, сработает ли это в большинстве браузеров.

Вам нужна библиотека в реальном времени.

Одним из примеров является Ratchet http://socketo.me/

Часть, которая заботится о пабе sub, обсуждена в http://socketo.me/docs/wamp

Ограничение здесь заключается в том, что PHP также должен инициировать изменяемые данные.

Другими словами, это волшебным образом не позволит вам подписаться на обновления MySQL. Но если вы можете редактировать код настройки MySQL, то можете добавить туда часть публикации.

Я согласен с консенсусом, что PHP здесь не лучшее решение. Вам действительно нужно искать специальные технологии реального времени для решения этой асинхронной проблемы доставки данных с вашего сервера вашим клиентам. Похоже, вы пытаетесь реализовать HTTP-длинный опрос, который не так-то просто решить кросс-браузер. Это неоднократно решалось разработчиками продуктов Comet, поэтому я бы посоветовал вам взглянуть на решение Comet или, что еще лучше, на решение WebSocket с резервной поддержкой для старых браузеров.

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

Другие вопросы по тегам