phpconcurrencycometsemaphorereverse-ajax

How to implement event listening in PHP


here is my problem: I have a script (let's call it comet.php) whic is requsted by an AJAX client script and wait for a change to happen like this:

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

I don't like this too much, it's not very scalable and it's (imho) "bad practice" I would like to improve this behaviour with a semaphore(?) or anyway concurrent programming technique. Can you please give me some tips on how to handle this? (I know, it's not a short answer, but a starting point would be enough.)

Edit: what about LibEvent?


Solution

  • You can solve this problem using ZeroMQ.

    ZeroMQ is a library that provides supercharged sockets for plugging things (threads, processes and even separate machines) together.

    I assume you're trying to push data from the server to the client. Well, a good way to do that is using the EventSource API (polyfills available).

    client.js

    Connects to stream.php through 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

    This is a long-running process that listens for incoming messages and sends them out to anyone listening.

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

    Every user connecting to the site gets his own stream.php. This script is long-running and waits for any messages from the router. Once it gets a new message, it will output this message in EventSource format.

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

    To send messages to all users, just send them to the router. The router will then distribute that message to all listening streams. Here's an example:

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

    This should prove that you do not need node.js to do realtime programming. PHP can handle it just fine.

    Apart from that, socket.io is a really nice way of doing this. And you could connect to socket.io to your PHP code via ZeroMQ easily.

    See also