2016-03-29 5 views
1

Ich benutze curl_multi, um Dateien über das herunterzuladen, was ich nur als "etwas behinderten Internet-Typ" der Verbindung beschreiben kann.Wie man gelöschte Verbindungen mit curl korrekt behandelt

Wenn Sie einen Standardwert für CURLOPT_TIMEOUT_MS verwenden, bleibt die Anwendung unendlich lange hängen. Da die Dateien ziemlich groß sind (das Herunterladen einer 300-KB-Datei dauert etwa 20 Minuten), konfiguriere ich sie mit einem geeigneten Wert von 1.800.000 (30 Minuten). Das Problem ist, wenn ich die Verbindung 5 Minuten nach dem Download verliere, muss ich 25 Minuten warten, bis der Handle freigegeben wird.

Meine erste Idee, um das Problem zu lösen, war ein kleineres Timeout zu verwenden, etwas im Bereich von 30 Sekunden, dann überprüfen Sie curl_info für ein Timeout-Ereignis. Wenn das Zeitlimit überschritten wird, starten Sie den Prozess mithilfe eines Bereichsheaders neu.

Es gibt hier jedoch einen schwerwiegenden Fehler, der Server kann die mehreren Verbindungen als Flutversuch sehen und mich blockieren, oder der Server unterstützt möglicherweise den Bereichskopf nicht (der Download wird vom ersten Byte aus gestartet).

Gibt es eine alternative Möglichkeit, eine verlorene oder zurückgesetzte Verbindung zu erkennen?

Ein Punkt zu beachten, mein Anruf an curl_multi_select verwendet einen Timeout-Wert, so kann ich Code ausführen, ohne auf curl warten zu müssen, um zu veröffentlichen.

Antwort

1

Wenn Sie curl_multi_select mit einem Timeout verwenden, scheint es, dass Sie in der Lage sein sollten, die Verbindung zu schließen, wenn einige oder alle Handles für einen bestimmten Zeitraum keine Daten erhalten haben. Das einzige Problem ist, dass Sie nicht wissen, welcher Griff hat keine Daten

erhalten
$timeout  = 45; // abort after 45 seconds with no data 
$lastReceived = 0; // time data was last received 

while ($active && $mrc == CURLM_OK) { 
    if (curl_multi_select($mh) != -1) { 
     do { 
      $mrc = curl_multi_exec($mh, $active); 
      $lastReceived = time(); 
     } while ($mrc == CURLM_CALL_MULTI_PERFORM); 
    } else { 
     if (time() - $lastReceived > $timeout) { 
      // no data received within timeout 
      // close cURL handles & multi connection and restart 
     } 
    } 
} 

, die funktionieren könnten, aber nicht geben Sie körnige Kontrolle über bestimmte Griffe finden.

Der beste Weg, mit Timeouts in einer Situation zu arbeiten, in der ich sie erkennen möchte, lange bevor ein Fehler auftritt, ist CURLOPT_PROGRESSFUNCTION in Verbindung mit CURLOPT_WRITEFUNCTION.

Ich werde den Code nicht in die Multi-Schnittstelle konvertieren, aber Sie können es in Ihren vorhandenen Code basierend auf Ihren Bedürfnissen arbeiten.

Die Idee ist, Ihr eigenes Timeout zu definieren und zu verfolgen, wann das Handle zuletzt Daten erhalten hat. Wenn in diesem Zeitraum keine Daten empfangen wurden, können Sie die Übertragung abbrechen und erneut beginnen.

Ich verwende dies in der Produktion, wo Daten in jeder Sekunde kommen sollten, so dass ich Timeouts früh erkennen möchte. Es funktioniert sehr gut für alle Arten von Verbindungsproblemen und Tests zeigten, dass es die Timeouts viel schneller erkannt hat, als es für cURL nötig gewesen wäre, um Fehler zu vermeiden.

Ein Nebeneffekt von CURLOPT_WRITEFUNCTION verwendet wird, ist, dass Sie die Daten speichern müssen, werden, wie es gelesen hat und nicht verwenden CURLOPT_RETURNTRANSFER die Daten von curl_exec

<?php 

$readTimeout = 45; // number of seconds to time out after if no data received 
$lastReceived = 0; // time data was last received on the handle 
$buffer  = ''; // buffer for storing response (you'll need one for each handle) 

//...when initializing cURL 
curl_setopt($ch, CURLOPT_NOPROGRESS, false); 
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, 'progressFunction'); 
curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'writeFunction'); 
// don't set anything for CURLOPT_RETURNTRANSFER 

$result = curl_exec($ch); 
$info = curl_getinfo($ch); 
$error = curl_errno($ch); 
// $result will be false on any failure or timeout 
// check $info['http_code'] for HTTP response status 
// $error will be empty if no error occurred 
// on success, $buffer will contain the full response body 
// if failed, you can try reconnecting and resending the request until successful 

//... 

function writeFunction($handle, $data) 
{ 
    global $lastReceived, $buffer; // <-- I use class properties instead 

    $lastReceived = time(); 
    $size = strlen($data); 

    $buffer .= $data; 

    return $size; 
} 

function progressFunction($ch, $dltotal, $dlnow, $ultotal, $ulnow) 
{ 
    global $lastReceived, $readTimeout; 

    $time = time(); 

    if ($time - $lastReceived > $readTimeout) { 
     // set error state - no data received within timeout 
     return 1; // non-zero causes cURL to disconnect 
    } 

    return 0; 
} 

zu bekommen leider Es ist nicht sehr einfach, aber es funktioniert gut zu tun, was du machen willst. Ich hoffe, das hilft.

+0

Das größte Problem, das ich mit dem Timeout habe, ist, dass es die Verbindung beendet. Ihre vorgeschlagene Methode, die Downloadgröße innerhalb von Zeitbereichen zu verfolgen, funktioniert einwandfrei. Danke, dass Sie sich die Zeit genommen haben, eine detaillierte Antwort zu schreiben. – Twifty

+0

Gern geschehen, hoffe es hilft.Mein Anwendungsfall war ziemlich spezifisch und ich hatte anderen Code sowohl in der progressFunction als auch in writeFunction, um andere Dinge zu tun, die ich für das Beispiel entfernt habe. Um zu erweitern, lebt die Verbindungs-/Anforderungslogik in einer Endlosschleife, die im Grunde für immer läuft und sich nach einem Timeout/Fehler wieder verbindet (entweder von progressFunction oder natürlich von cURL). – drew010

Verwandte Themen