ZetCode

PHP curl_multi_add_handle 函数

最后修改日期:2025 年 4 月 11 日

PHP curl_multi_add_handle 函数将一个 cURL 句柄添加到一组 cURL 句柄中。它用于同时执行多个 HTTP 请求。这使得 Web 请求能够高效地并行处理。

基本定义

curl_multi_add_handle 函数将一个标准的 cURL 句柄添加到多句柄中。成功时返回 0,否则返回 CURLM_XXX 错误之一。

语法:curl_multi_add_handle(CurlMultiHandle $multi_handle, CurlHandle $handle): int。多句柄必须使用 curl_multi_init() 创建。完成后务必使用 curl_multi_remove_handle() 删除句柄。

基本的多个 GET 请求

此示例演示了如何并行执行多个 GET 请求。

basic_multi_get.php
<?php

declare(strict_types=1);

$urls = [
    'https://jsonplaceholder.typicode.com/posts/1',
    'https://jsonplaceholder.typicode.com/posts/2',
    'https://jsonplaceholder.typicode.com/posts/3'
];

$mh = curl_multi_init();
$handles = [];

foreach ($urls as $url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($mh, $ch);
    $handles[] = $ch;
}

$running = null;
do {
    curl_multi_exec($mh, $running);
    curl_multi_select($mh);
} while ($running > 0);

foreach ($handles as $ch) {
    echo curl_multi_getcontent($ch) . "\n";
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}

curl_multi_close($mh);

此代码同时获取三个帖子。我们为每个 URL 创建单独的 cURL 句柄,并将它们添加到多句柄中。循环处理所有请求直到完成。最后,我们检索并输出每个响应。

使用回调处理响应

此示例展示了如何使用回调在响应完成时处理它们。

callback_responses.php
<?php

declare(strict_types=1);

$urls = [
    'https://jsonplaceholder.typicode.com/users/1',
    'https://jsonplaceholder.typicode.com/users/2',
    'https://jsonplaceholder.typicode.com/users/3'
];

$mh = curl_multi_init();
$handles = [];

foreach ($urls as $i =>  $url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_PRIVATE, $i); // Store index
    curl_multi_add_handle($mh, $ch);
    $handles[$i] = $ch;
}

do {
    $status = curl_multi_exec($mh, $running);
    if ($running) {
        curl_multi_select($mh);
    }
    
    while ($info = curl_multi_info_read($mh)) {
        $ch = $info['handle'];
        $index = curl_getinfo($ch, CURLINFO_PRIVATE);
        $content = curl_multi_getcontent($ch);
        
        echo "Response $index: " . substr($content, 0, 50) . "...\n";
        
        curl_multi_remove_handle($mh, $ch);
        curl_close($ch);
    }
} while ($running && $status == CURLM_OK);

curl_multi_close($mh);

我们使用 curl_multi_info_read 在响应可用时进行处理。每个句柄通过 CURLOPT_PRIVATE 存储其索引。这使我们能够跟踪每个响应属于哪个请求。响应会立即得到处理。

具有不同数据的 POST 请求

此示例演示了具有不同数据负载的并行 POST 请求。

multi_post.php
<?php

declare(strict_types=1);

$posts = [
    ['title' =>  'First Post', 'body' =>  'Content 1', 'userId' =>  1],
    ['title' =>  'Second Post', 'body' =>  'Content 2', 'userId' =>  2],
    ['title' =>  'Third Post', 'body' =>  'Content 3', 'userId' =>  3]
];

$mh = curl_multi_init();
$handles = [];

foreach ($posts as $i =>  $post) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://jsonplaceholder.typicode.com/posts');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_PRIVATE, $i);
    curl_multi_add_handle($mh, $ch);
    $handles[$i] = $ch;
}

do {
    $status = curl_multi_exec($mh, $running);
    if ($running) {
        curl_multi_select($mh);
    }
    
    while ($info = curl_multi_info_read($mh)) {
        $ch = $info['handle'];
        $index = curl_getinfo($ch, CURLINFO_PRIVATE);
        $response = curl_multi_getcontent($ch);
        
        echo "Post $index created: " . $response . "\n";
        
        curl_multi_remove_handle($mh, $ch);
        curl_close($ch);
    }
} while ($running && $status == CURLM_OK);

curl_multi_close($mh);

我们并行发送三个不同的 POST 请求。每个请求都有唯一的 JSON 数据。响应在完成时进行处理。我们通过 CURLOPT_PRIVATE 来维护请求和响应之间的关联。

并行请求中的错误处理

此示例展示了对多个并发请求的正确错误处理。

error_handling.php
<?php

declare(strict_types=1);

$urls = [
    'https://jsonplaceholder.typicode.com/posts/1', // Valid
    'https://jsonplaceholder.typicode.com/nonexistent', // 404
    'https://invalid-url.example.com' // Invalid
];

$mh = curl_multi_init();
$handles = [];

foreach ($urls as $i =>  $url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_PRIVATE, $i);
    curl_setopt($ch, CURLOPT_FAILONERROR, true);
    curl_multi_add_handle($mh, $ch);
    $handles[$i] = $ch;
}

do {
    $status = curl_multi_exec($mh, $running);
    if ($running) {
        curl_multi_select($mh);
    }
    
    while ($info = curl_multi_info_read($mh)) {
        $ch = $info['handle'];
        $index = curl_getinfo($ch, CURLINFO_PRIVATE);
        
        if ($info['result'] !== CURLE_OK) {
            echo "Request $index failed: " . curl_error($ch) . "\n";
        } else {
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if ($httpCode >= 400) {
                echo "Request $index returned HTTP $httpCode\n";
            } else {
                echo "Request $index succeeded\n";
            }
        }
        
        curl_multi_remove_handle($mh, $ch);
        curl_close($ch);
    }
} while ($running && $status == CURLM_OK);

curl_multi_close($mh);

我们演示了如何处理并行请求中的不同类型的错误。CURLOPT_FAILONERROR 有助于检测 HTTP 错误。我们同时检查 cURL 错误和 HTTP 状态码。每个错误都与其请求正确关联。

限制并发请求

此示例展示了如何限制并发请求的数量。

concurrency_limit.php
<?php

declare(strict_types=1);

$urls = [
    'https://jsonplaceholder.typicode.com/posts/1',
    'https://jsonplaceholder.typicode.com/posts/2',
    'https://jsonplaceholder.typicode.com/posts/3',
    'https://jsonplaceholder.typicode.com/posts/4',
    'https://jsonplaceholder.typicode.com/posts/5'
];

$maxConcurrent = 2; // Maximum concurrent requests
$mh = curl_multi_init();
$activeHandles = [];
$allHandles = [];
$processed = 0;

while ($processed < count($urls)) {
    // Add new handles until we reach max concurrent or run out of URLs
    while (count($activeHandles) < $maxConcurrent && $processed < count($urls)) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $urls[$processed]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_PRIVATE, $processed);
        curl_multi_add_handle($mh, $ch);
        $activeHandles[] = $ch;
        $allHandles[$processed] = $ch;
        $processed++;
    }
    
    // Process active handles
    do {
        $status = curl_multi_exec($mh, $running);
        if ($running) {
            curl_multi_select($mh);
        }
        
        while ($info = curl_multi_info_read($mh)) {
            $ch = $info['handle'];
            $index = curl_getinfo($ch, CURLINFO_PRIVATE);
            
            if ($info['result'] === CURLE_OK) {
                echo "Request $index completed: " . 
                    substr(curl_multi_getcontent($ch), 0, 30) . "...\n";
            }
            
            // Remove completed handle from active list
            $key = array_search($ch, $activeHandles, true);
            if ($key !== false) {
                unset($activeHandles[$key]);
            }
            
            curl_multi_remove_handle($mh, $ch);
            curl_close($ch);
        }
    } while ($running && $status == CURLM_OK);
}

curl_multi_close($mh);

我们维护一个活动句柄池,数量限制为 $maxConcurrent。随着请求完成,新的请求会被添加到池中。这可以防止服务器因过多的同时请求而过载。这种方法对于大批量处理很有用。

最佳实践

来源

PHP curl_multi_add_handle 文档

本教程介绍了 PHP curl_multi_add_handle 函数,并通过实际示例展示了各种场景下的并行请求处理。

作者

我叫 Jan Bodnar,是一名热情的程序员,拥有丰富的编程经验。我自 2007 年起撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

所有 PHP cURL 教程列表。