Chiamata di funzione asincrona in PHP


86

Sto lavorando su un'applicazione web PHP e ho bisogno di eseguire alcune operazioni di rete nella richiesta come il recupero di qualcuno dal server remoto in base alla richiesta dell'utente.

È possibile simulare un comportamento asincrono in PHP dato che devo passare alcuni dati a una funzione e ho bisogno anche di un output da essa.

Il mio codice è come:

<?php

     $data1 = processGETandPOST();
     $data2 = processGETandPOST();
     $data3 = processGETandPOST();

     $response1 = makeNetworkCall($data1);
     $response2 = makeNetworkCall($data2);
     $response3 = makeNetworkCall($data3);

     processNetworkResponse($response1);
     processNetworkResponse($response2);
     processNetworkResponse($response3);

     /*HTML and OTHER UI STUFF HERE*/

     exit;
?>

Ogni operazione di rete richiede circa 5 secondi per essere completata aggiungendo un totale di 15 secondi al tempo di risposta della mia applicazione dato che faccio 3 richieste.

La funzione makeNetworkCall () esegue semplicemente una richiesta HTTP POST.

Il server remoto è un'API di terze parti, quindi non ho alcun controllo su di esso.

PS: per favore non rispondere dando suggerimenti su AJAX o altre cose. Attualmente sto cercando se posso farlo tramite PHP potrebbe essere con un'estensione C ++ o qualcosa del genere.


Prova a utilizzare CURLper
attivare le

Credo che la risposta sia qui: stackoverflow.com/questions/13846192/… Nota rapida: usa il threading
DRAX


Puoi utilizzare la funzione stream_select di PHP per eseguire codice non bloccante. Reagire utilizza per creare un ciclo di eventi-driven simile a node.js .
Quinn Comendant

Risposte:


20

Al giorno d'oggi, è meglio usare le code che i thread (per coloro che non usano Laravel ci sono tonnellate di altre implementazioni là fuori come questa ).

L'idea di base è che il tuo script PHP originale mette in coda attività o lavori. Quindi hai lavoratori in coda in esecuzione altrove, che eliminano i lavori dalla coda e iniziano a elaborarli indipendentemente dal PHP originale.

I vantaggi sono:

  1. Scalabilità: puoi semplicemente aggiungere nodi di lavoro per stare al passo con la domanda. In questo modo, le attività vengono eseguite in parallelo.
  2. Affidabilità: i gestori code moderni come RabbitMQ, ZeroMQ, Redis, ecc., Sono realizzati per essere estremamente affidabili.


8

Non ho una risposta diretta, ma potresti voler esaminare queste cose:


3

cURL sarà la tua unica vera scelta qui (o quella, o usando socket non bloccanti e una logica personalizzata).

Questo link dovrebbe indirizzarti nella giusta direzione. Non c'è elaborazione asincrona in PHP, ma se stai cercando di fare più richieste web simultanee, cURL multi si prenderà cura di questo per te.


2

Penso che se l'HTML e altre cose dell'interfaccia utente necessitano della restituzione dei dati, non ci sarà un modo per asincronizzarli.

Credo che l'unico modo per farlo in PHP sarebbe registrare una richiesta in un database e fare un controllo cron ogni minuto, o usare qualcosa come l'elaborazione della coda Gearman, o forse exec () un processo da riga di comando

Nel frattempo la tua pagina php dovrebbe generare un po 'di html o js che lo faccia ricaricare ogni pochi secondi per controllare lo stato di avanzamento, non è l'ideale.

Per aggirare il problema, quante richieste diverse ti aspetti? Potresti scaricarli tutti automaticamente ogni ora circa e salvarli in un database?


1

Questa vecchia domanda ha una nuova risposta. Ci sono alcune soluzioni "asincrone" per PHP oggigiorno (che sono equivalenti al multiprocesso di Python, nel senso che generano nuovi processi PHP indipendenti piuttosto che gestirli a livello di framework)

Le due soluzioni che ho visto sono

Provali!



0

Penso che qui sia necessario un po 'di codice sulla soluzione cURL, quindi condividerò il mio (è stato scritto mescolando diverse fonti come il manuale PHP ei commenti).

Esegue alcune richieste HTTP parallele (domini in $aURLs) e stampa le risposte una volta che ognuna è stata completata (e le memorizza $doneper altri possibili usi).

Il codice è più lungo del necessario perché la parte di stampa in tempo reale e l'eccesso di commenti, ma sentiti libero di modificare la risposta per migliorarla:

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

Un modo è usare pcntl_fork()in una funzione ricorsiva.

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

Una cosa pcntl_fork()è che quando si esegue lo script tramite Apache, non funziona (non è supportato da Apache). Quindi, un modo per risolvere il problema è eseguire lo script utilizzando il php cli, ad esempio: exec('php fork.php',$output);da un altro file. Per fare ciò avrai due file: uno caricato da Apache e uno eseguito exec()dall'interno del file caricato da Apache in questo modo:

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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.