Asynchronous Function Call in PHP

前端 未结 7 1421
逝去的感伤
逝去的感伤 2020-11-30 20:54

I am working on an a PHP web application and i need to perform some network operations in the request like fetching someone from remote server based on user\'s request.

相关标签:
7条回答
  • 2020-11-30 21:32

    I think if the HTML and other UI stuff needs the data returned then there is not going to be a way to async it.

    I believe the only way to do this in PHP would be to log a request in a database and have a cron check every minute, or use something like Gearman queue processing, or maybe exec() a command line process

    In the meantime you php page would have to generate some html or js that makes it reload every few seconds to check on progress, not ideal.

    To sidestep the issue, how many different requests are you expecting? Could you download them all automatically every hour or so and save to a database?

    0 讨论(0)
  • 2020-11-30 21:33

    I dont have a direct answer, but you might want to look into these things:

    • Recoil project - https://github.com/recoilphp/recoil
    • php's LibEvent extension? http://www.php.net/manual/en/book.libevent.php
    • process forking http://www.php.net/manual/en/function.pcntl-fork.php
    • message brokers, i.e. you could start workers to make the HTTP calls and once that job is done, insert a new job describing the work that needs to be done in order to process the cached HTTP response body.
    0 讨论(0)
  • 2020-11-30 21:43

    Nowadays, it's better to use queues than threads (for those who don't use Laravel there are tons of other implementations out there like this).

    The basic idea is, your original PHP script puts tasks or jobs into a queue. Then you have queue job workers running elsewhere, taking jobs out of the queue and starts processing them independently of the original PHP.

    The advantages are:

    1. Scalability - you can just add worker nodes to keep up with demand. In this way, tasks are run in parallel.
    2. Reliability - modern queue managers such as RabbitMQ, ZeroMQ, Redis, etc, are made to be extremely reliable.
    0 讨论(0)
  • 2020-11-30 21:43

    I think some code about the cURL solution is needed here, so I will share mine (it was written mixing several sources as the PHP Manual and comments).

    It does some parallel HTTP requests (domains in $aURLs) and print the responses once each one is completed (and stored them in $done for other possible uses).

    The code is longer than needed because the realtime print part and the excess of comments, but feel free to edit the answer to improve it:

    <?php
    /* Strategies to avoid output buffering, ignore the block if you don't want to print the responses before every cURL is completed */
    ini_set('output_buffering', 'off'); // Turn off output buffering
    ini_set('zlib.output_compression', false); // Turn off PHP output compression       
    //Flush (send) the output buffer and turn off output buffering
    ob_end_flush(); while (@ob_end_flush());        
    apache_setenv('no-gzip', true); //prevent apache from buffering it for deflate/gzip
    ini_set('zlib.output_compression', false);
    header("Content-type: text/plain"); //Remove to use HTML
    ini_set('implicit_flush', true); // Implicitly flush the buffer(s)
    ob_implicit_flush(true);
    header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
    $string=''; for($i=0;$i<1000;++$i){$string.=' ';} output($string); //Safari and Internet Explorer have an internal 1K buffer.
    //Here starts the program output
    
    function output($string){
        ob_start();
        echo $string;
        if(ob_get_level()>0) ob_flush();
        ob_end_clean();  // clears buffer and closes buffering
        flush();
    }
    
    function multiprint($aCurlHandles,$print=true){
        global $done;
        // iterate through the handles and get your content
        foreach($aCurlHandles as $url=>$ch){
            if(!isset($done[$url])){ //only check for unready responses
                $html = curl_multi_getcontent($ch); //get the content           
                if($html){
                    $done[$url]=$html;
                    if($print) output("$html".PHP_EOL);
                }           
            }
        }
    };
    
    function full_curl_multi_exec($mh, &$still_running) {
        do {
          $rv = curl_multi_exec($mh, $still_running); //execute the handles 
        } while ($rv == CURLM_CALL_MULTI_PERFORM); //CURLM_CALL_MULTI_PERFORM means you should call curl_multi_exec() again because there is still data available for processing
        return $rv;
    } 
    
    set_time_limit(60); //Max execution time 1 minute
    
    $aURLs = array("http://domain/script1.php","http://domain/script2.php");  // array of URLs
    
    $done=array();  //Responses of each URL
    
        //Initialization
        $aCurlHandles = array(); // create an array for the individual curl handles
        $mh = curl_multi_init(); // init the curl Multi and returns a new cURL multi handle
        foreach ($aURLs as $id=>$url) { //add the handles for each url        
            $ch = curl_init(); // init curl, and then setup your options
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // returns the result - very important
            curl_setopt($ch, CURLOPT_HEADER, 0); // no headers in the output
            $aCurlHandles[$url] = $ch;
            curl_multi_add_handle($mh,$ch);
        }
    
        //Process
        $active = null; //the number of individual handles it is currently working with
        $mrc=full_curl_multi_exec($mh, $active); 
        //As long as there are active connections and everything looks OK…
        while($active && $mrc == CURLM_OK) { //CURLM_OK means is that there is more data available, but it hasn't arrived yet.  
            // Wait for activity on any curl-connection and if the network socket has some data…
            if($descriptions=curl_multi_select($mh,1) != -1) {//If waiting for activity on any curl_multi connection has no failures (1 second timeout)     
                usleep(500); //Adjust this wait to your needs               
                //Process the data for as long as the system tells us to keep getting it
                $mrc=full_curl_multi_exec($mh, $active);        
                //output("Still active processes: $active".PHP_EOL);        
                //Printing each response once it is ready
                multiprint($aCurlHandles);  
            }
        }
    
        //Printing all the responses at the end
        //multiprint($aCurlHandles,false);      
    
        //Finalize
        foreach ($aCurlHandles as $url=>$ch) {
            curl_multi_remove_handle($mh, $ch); // remove the handle (assuming  you are done with it);
        }
        curl_multi_close($mh); // close the curl multi handler
    ?>
    
    0 讨论(0)
  • 2020-11-30 21:44

    cURL is going to be your only real choice here (either that, or using non-blocking sockets and some custom logic).

    This link should send you in the right direction. There is no asynchronous processing in PHP, but if you're trying to make multiple simultaneous web requests, cURL multi will take care of that for you.

    0 讨论(0)
  • 2020-11-30 21:51

    One way is to use pcntl_fork() in a recursive function.

    function networkCall(){
      $data = processGETandPOST();
      $response = makeNetworkCall($data);
      processNetworkResponse($response);
      return true;
    }
    
    function runAsync($times){
      $pid = pcntl_fork();
      if ($pid == -1) {
        die('could not fork');
      } else if ($pid) {
        // we are the parent
        $times -= 1;
        if($times>0)
          runAsync($times);
        pcntl_wait($status); //Protect against Zombie children
      } else {
        // we are the child
        networkCall();
        posix_kill(getmypid(), SIGKILL);
      }
    }
    
    runAsync(3);
    

    One thing about pcntl_fork() is that when running the script by way of Apache, it doesn't work (it's not supported by Apache). So, one way to resolve that issue is to run the script using the php cli, like: exec('php fork.php',$output); from another file. To do this you'll have two files: one that's loaded by Apache and one that's run with exec() from inside the file loaded by Apache like this:

    apacheLoadedFile.php

    exec('php fork.php',$output);
    

    fork.php

    function networkCall(){
      $data = processGETandPOST();
      $response = makeNetworkCall($data);
      processNetworkResponse($response);
      return true;
    }
    
    function runAsync($times){
      $pid = pcntl_fork();
      if ($pid == -1) {
        die('could not fork');
      } else if ($pid) {
        // we are the parent
        $times -= 1;
        if($times>0)
          runAsync($times);
        pcntl_wait($status); //Protect against Zombie children
      } else {
        // we are the child
        networkCall();
        posix_kill(getmypid(), SIGKILL);
      }
    }
    
    runAsync(3);
    
    0 讨论(0)
提交回复
热议问题