Связь между C- и PHP-программой

Я пишу программу для моего Raspberry Pi, которая состоит из двух основных частей:

  1. C-программа, использующая Spotify-API "Libspotify" для поиска музыки и ее воспроизведения.
  2. PHP-программа, работающая на веб-сервере apache2, для управления Spotify-программой с ПК или смартфона в моей локальной сети.

Связь между этими двумя отдельными программами работает через пару файлов.

C-часть для приема пользовательских входных данных вызывается раз в секунду и работает так:

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

Итак, через fwrite() в PHP я могу отправлять команды на C-программу.

После этого C-программа анализирует входные данные и записывает результаты в файл "results". Как только это будет сделано, я записываю последнее содержимое файла "Communication" в файл "last_query", поэтому в PHP я вижу, когда все результаты записываются в "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(); 
}

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

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

1 ответ

Решение

Я полагаю, что вы сами пишете как PHP, так и код C, и что у вас есть доступ к компиляторам и так далее? Я бы не стал запускать другой процесс и использовать межпроцессное взаимодействие, а написать расширение PHP C++, которое сделает все это за вас.

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

Вы можете использовать такую ​​программу:

#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;
    }
}

Вы можете использовать эту очередь инструкций из скрипта PHP следующим образом:

<?php

$queue = new InstructionQueue();

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

echo($queue->result());

В качестве примера я добавил только глупую реализацию, которая спит некоторое время, но вы можете запустить там свои функции вызова API.

Расширение использует библиотеку PHP-CPP (см. http://www.php-cpp.com/).

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