Установить глобальное ограничение скорости загрузки и загрузки PHP-CURL?

Я использую CURL в нескольких отдельных PHP-скриптах для загрузки/выгрузки файлов. Есть ли способ установить ГЛОБАЛЬНОЕ (не для каждого дескриптора завитка) ограничение скорости UL/DL?

К сожалению, вы можете установить ограничение скорости только для одного сеанса в CURL, но оно не является динамическим.

В качестве серверной ОС используется Ubuntu, есть ли альтернативный способ по-другому ограничить процессы CURL?

Спасибо


person k.hubenbecker    schedule 25.05.2018    source источник
comment
Возможный дубликат Ограничение пропускной способности загрузки с помощью cURL   -  person Fabian Schöner    schedule 25.05.2018
comment
@FabianSchöner ошибается, это установит ограничение пропускной способности для каждого дескриптора завитка, а не системное глобальное ограничение пропускной способности.   -  person hanshenrik    schedule 25.05.2018
comment
вы можете перестать отмечать это как дубликат? это НЕ дубликат, вопрос, предлагаемый в качестве дубликата, касается того, как ограничить пропускную способность для одного дескриптора curl, а не как установить глобальную скорость системы/процесса.   -  person hanshenrik    schedule 25.05.2018


Ответы (1)


curl/libcurl не имеет функции для разделения ограничения пропускной способности между дескрипторами curl_easy, а тем более между различными процессами. вместо этого я предлагаю использовать демон curl для обеспечения ограничений пропускной способности. с клиентом, выглядящим примерно так

class curl_daemon_response{
    public $stdout;
    public $stderr;
}
function curl_daemon(array $curl_options):curl_daemon_response{
    $from_big_uint64_t=function(string $i): int {
        $arr = unpack ( 'Juint64_t', $i );
        return $arr ['uint64_t'];
    };
    $to_big_uint64_t=function(int $i): string {
        return pack ( 'J', $i );
    };
    $conn = stream_socket_client("unix:///var/run/curl_daemon", $errno, $errstr, 3);
    if (!$conn) {
        throw new \RuntimeError("failed to connect to /var/run/curl_daemon! $errstr ($errno)");
    }
    stream_set_blocking($conn,true);
    $curl_options=serialize($curl_options);
    fwrite($conn,$to_big_uint64_t(strlen($curl_options)).$curl_options);
    $stdoutLen=$from_big_uint64_t(fread($conn,8));
    $stdout=fread($conn,$stdoutLen);
    $stderrLen=$from_big_uint64_t(fread($conn,8));
    $stderr=fread($conn,$stderrLen);
    $ret=new curl_daemon_response();
    $ret->stdout=$stdout;
    $ret->stderr=$stderr;
    fclose($conn);
    return $ret;
}

и демон выглядит примерно так

<?php
declare(strict_types=1);
const MAX_DOWNLOAD_SPEED=1000*1024; // 1000 kilobytes
const MINIMUM_DOWNLOAD_SPEED=100; // 100 bytes per second,
class Client{
    public $id;
    public $socket;
    public $curl;
    public $arguments;
    public $stdout;
    public $stderr;
}
$clients=[];
$mh=curl_multi_init();
$srv = stream_socket_server("unix:///var/run/curl_daemon", $errno, $errstr);
if (!$srv) {
  throw new \RuntimeError("failed to create unix socket /var/run/curl_daemon! $errstr ($errno)");
}
stream_set_blocking($srv,false);
while(true){
    getNewClients();
    $cc=count($clients);
    if(!$cc){
        sleep(1); // nothing to do.
        continue;
    }
    curl_multi_exec($mh, $running);
    if($running!==$cc){
        // at least 1 of the curls finished!
        while(false!==($info=curl_multi_info_read($mh))){
            $key=curl_getinfo($info['handle'],CURLINFO_PRIVATE);
            curl_multi_remove_handle($mh,$clients[$key]->curl);
            curl_close($clients[$key]->curl);
            $stdout=file_get_contents(stream_get_meta_data($clients[$key]->stdout)['uri']); // https://bugs.php.net/bug.php?id=76268
            fclose($clients[$key]->stdout);
            $stderr=file_get_contents(stream_get_meta_data($clients[$key]->stderr)['uri']); // https://bugs.php.net/bug.php?id=76268
            fclose($clients[$key]->stderr);
            $sock=$clients[$key]->socket;
            fwrite($sock,to_big_uint64_t(strlen($stdout)).$stdout.to_big_uint64_t(strlen($stderr)).$stderr);
            fclose($sock);
            echo "finished request #{$key}!\n";
            unset($clients[$key],$key,$stdout,$stderr,$sock);
        }
        updateSpeed();
    }
    curl_multi_select($mh);
}

function updateSpeed(){
    global $clients;
    static $old_speed=-1;
    if(empty($clients)){
        return;
    }
    $clientsn=count($clients);
    $per_handle_speed=MAX(MINIMUM_DOWNLOAD_SPEED,(MAX_DOWNLOAD_SPEED/$clientsn));
    if($per_handle_speed===$old_speed){
        return;
    }
    $old_speed=$per_handle_speed;
    echo "new per handle speed: {$per_handle_speed} - clients: {$clientsn}\n";
    foreach($clients as $client){
        /** @var Client $client */
        curl_setopt($client->curl,CURLOPT_MAX_RECV_SPEED_LARGE,$per_hande_speed);
    }
}


function getNewClients(){
    global $clients,$srv,$mh;
    static $counter=-1;
    $newClients=false;
    while(false!==($new=stream_socket_accept($srv,0))){
        ++$counter;
        $newClients=true;
        echo "new client! request #{$counter}\n";
        stream_set_blocking($new,true);
        $tmp=new Client();
        $tmp->id=$counter;
        $tmp->socket=$new;
        $tmp->curl=curl_init();
        $tmp->stdout=tmpfile();
        $tmp->stderr=tmpfile();
        $size=from_big_uint64_t(fread($new,8));
        $arguments=fread($new,$size);
        $arguments=unserialize($arguments);
        assert(is_array($arguments));
        $tmp->arguments=$arguments;
        curl_setopt_array($tmp->curl,$arguments);
        curl_setopt_array($tmp->curl,array(
            CURLOPT_FILE=>$tmp->stdout,
            CURLOPT_STDERR=>$tmp->stderr,
            CURLOPT_VERBOSE=>1,
            CURLOPT_PRIVATE=>$counter
        ));
        curl_multi_add_handle($mh,$tmp->curl);
    }
    if($newClients){
        updateSpeed();
    }
}

function from_big_uint64_t(string $i): int {
    $arr = unpack ( 'Juint64_t', $i );
    return $arr ['uint64_t'];
}
function to_big_uint64_t(int $i): string {
    return pack ( 'J', $i );
}

примечание: это полностью непроверенный код, т.к. моя среда разработки умерла буквально пару часов назад, и я все это писал в notepad++. (мой dev env вообще не загружается, это виртуальная машина, не уверен, что что-то случилось, но еще не исправил)

Кроме того, код совсем не оптимизирован для передачи больших файлов, если вам нужно поддерживать передачу больших файлов таким образом (размеры, которые вы не хотите упаковывать в оперативную память, например, гигабайты +), тогда измените демон, чтобы он возвращал пути к файлам вместо записи все данные через сокет unix.

person hanshenrik    schedule 25.05.2018