在C-和PHP-程序之间进行通信

时间:2014-03-05 20:25:01

标签: php c communication libspotify

我正在为我的Raspberry Pi编写一个程序,它由两个主要部分组成:

  1. 使用Spotify-API“L​​ibspotify”搜索音乐和播放音乐的C程序。
  2. 在apache2-Webserver上运行的PHP程序,用于从本地网络中的PC或智能手机控制Spotify程序。
  3. 这两个独立程序之间的通信适用于几个文件。

    用于接收用户输入的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);
    }
    

    因此,通过PHP中的fwrite(),我可以向C程序发送命令。

    之后,C-Program解析输入并将结果写入“results”文件中。一旦完成,我将“通信”文件的最后内容写成“last_query”文件,因此在PHP中我可以看到,当整个结果写在“结果”中时:

    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 个答案:

答案 0 :(得分:0)

我想您自己编写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)。