0

I'm writing a program for my Raspberry Pi, that consists of two main parts:

  1. A C-Program that uses the Spotify-API "Libspotify" to search for music and for playing it.
  2. A PHP-program, running on an apache2-Webserver, to control the Spotify-program from a PC or Smartphone in my local network.

The communication between these two separate programs works over a couple of files.

The C-part for receiving the user inputs is being called once a second and works like this:

void get_input() {
    int i = 0, c;
    FILE *file;
    struct stat stat;
    char buffer[INPUT_BUFFER_SIZE];

    file = fopen(PATH_TO_COMMUNICATION, "r");
    if (file == NULL) {
        perror("Error opening PATH_TO_COMMUNICATION");
        return;
    }

    fstat(fileno(file), &stat);
    if (stat.st_size > 1) {
        while((c = fgetc(file)) != EOF && i < INPUT_BUFFER_SIZE) {
            buffer[i] = c;
            ++i;
        }
        buffer[i] = '\0';
        parse_input(buffer);
    }

    fclose(file);
    clear_file(PATH_TO_COMMUNICATION);
}

So, via fwrite() in PHP, I can send commands to the C-program.

Afterwards, the C-Program parses the input and writes the results in an "results"-file. Once this is done, I write the last contents of the "communication"-file an "last_query"-file, so in PHP i can see, when the entire results are written in the "results":

function search($query) {
    write_to_communication("search ".$query);
    do { // Wait for results
        $file = fopen("../tmp/last_query", "r");
        $line = fgets($file);
        fclose($file);
        time_nanosleep(0, 100000000);
    } while($line != $query);   
    echo get_result_json(); 
}

It does work already, but I don't like the way at all. There is a lot of polling and unneccessary opening and closing of different files. Also, in the worst case the program needs more than a second until it starts to do something with the user input. Furthermore, there can be race conditions, when the C-program tries to read the file during the PHP-program writes into it.

So, now to my question: What is the "right" way, to achieve a nice and clean communication between the two program parts? Is there some completely different way without the ugly polling and without race conditions? Or can I improve the existing code, so that it gets nicer?

Ansichtssache
  • 111
  • 1
  • 2
  • 9

1 Answers1

0

I suppose that you write both the PHP and the C code yourself, and that you have access to compilers and so on? What I would do is not at all start up a different process and use inter-process-communication, but write a PHP C++ extension that does this all for you.

The extension starts up a new thread, and this thread picks up all instruction from an in-memory instruction queue. When you're ready to pick up the result, the final instruction is sent to the thread (the instruction to close down), and when the thread is finally finished, you can pick up the result.

You could use a program like this:

#include <phpcpp.h>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <unistd.h>

/**
 *  Class that takes instructions from PHP, and that executes them in CPP code
 */
class InstructionQueue : public Php::Base
{
private:
    /**
     *  Queue with instructions (for simplicity, we store the instructions
     *  in strings, much cooler implementations are possible)
     *
     *  @var    std::queue
     */
    std::queue<std::string> _queue;

    /**
     *  The final result
     *  @var    std::string
     */
    std::string _result;

    /**
     *  Counter with number of instructions
     *  @var    int
     */
    int _counter = 0;

    /**
     *  Mutex to protect shared data
     *  @var    std::mutex
     */
    std::mutex _mutex;

    /**
     *  Condition variable that the thread uses to wait for more instructions
     *  @var    std::condition_variable
     */
    std::condition_variable _condition;

    /**
     *  Thread that processes the instructions
     *  @var    std::thread
     */
    std::thread _thread;


    /**
     *  Procedure to execute one specific instruction
     *  @param  instruction
     */
    void execute(const std::string &instruction)
    {
        // @todo 
        //
        //  add your own the implementation, for now we just
        //  add the instruction to the result, and sleep a while
        //  to pretend that this is a difficult algorithm

        // append the instruction to the result
        _result.append(instruction);
        _result.append("\n");

        // sleep for a while
        sleep(1);
   }

    /**
     *  Main procedure that runs the thread
     */
    void run()
    {
        // need the mutex to access shared resources
        std::unique_lock<std::mutex> lock(_mutex);

        // keep looping
        while (true)
        {
            // go wait for instructions
            while (_counter == 0) _condition.wait(lock);

            // check the number of instructions, leap out when empty
            if (_queue.size() == 0) return;

            // get instruction from the queue, and reduce queue size
            std::string instruction(std::move(_queue.front()));

            // remove front item from queue
            _queue.pop();

            // no longer need the lock
            lock.unlock();

            // run the instruction
            execute(instruction);

            // get back the lock for the next iteration of the main loop
            lock.lock();
        }
    }


public:
    /**
     *  C++ constructor
     */
    InstructionQueue() : _thread(&InstructionQueue::run, this) {}

    /**
     *  Copy constructor
     * 
     *  We just create a brand new queue when it is copied (copy constructor
     *  is required by the PHP-CPP library)
     * 
     *  @param  queue
     */
    InstructionQueue(const InstructionQueue &queue) : InstructionQueue() {}

    /**
     *  Destructor
     */
    virtual ~InstructionQueue() 
    {
        // stop the thread
        stop();
    }

    /**
     *  Method to add an instruction
     *  @param  params      Object representing PHP parameters
     */
    void add(Php::Parameters &params)
    {
        // first parameter holds the instruction
        std::string instruction = params[0];

        // need a mutex to access shared resources
        _mutex.lock();

        // add instruction
        _queue.push(instruction);

        // update instruction counter
        _counter++;

        // done with shared resources
        _mutex.unlock();

        // notify the thread
        _condition.notify_one();
    }

    /**
     *  Method to stop the thread
     */
    void stop()
    {
        // is the thread already finished?
        if (!_thread.joinable()) return;

        // thread is still running, send instruction to stop (which is the
        // same as not sending an instruction at all but just increasing the
        // instruction counter, lock mutex to access protected data
        _mutex.lock();

        // add instruction
        _counter++;

        // done with shared resources
        _mutex.unlock();

        // notify the thread
        _condition.notify_one();

        // wait for the thread to finish
        _thread.join();
    }

    /**
     *  Retrieve the result
     *  @return string
     */
    Php::Value result()
    {
        // stop the thread first
        stop();

        // return the result
        return _result;
    }
};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine 
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {

        // extension object
        static Php::Extension myExtension("InstructionQueue", "1.0");

        // description of the class so that PHP knows 
        // which methods are accessible
        Php::Class<InstructionQueue> myClass("InstructionQueue");

        // add methods
        myClass.method("add", &InstructionQueue::add);
        myClass.method("result", &InstructionQueue::result);

        // add the class to the extension
        myExtension.add(std::move(myClass));

        // return the extension
        return myExtension;
    }
}

You can use this instruction queue from a PHP script like this:

<?php

$queue = new InstructionQueue();

$queue->add("instruction 1");
$queue->add("instruction 2");
$queue->add("instruction 3");

echo($queue->result());

As an example I've only added a silly implementation that sleeps for a while, but you could run your API-calling functions in there.

The extension uses the PHP-CPP library (see http://www.php-cpp.com).