curl_multi_add_handle() 函数用于将一个标准的cURL句柄添加到cURL多句柄中。这是实现并行HTTP请求的关键步骤,只有添加到多句柄的cURL句柄才能通过curl_multi_exec()并行执行。
int curl_multi_add_handle ( resource $mh , resource $ch )
| 参数 | 描述 | 类型 | 必需 |
|---|---|---|---|
| mh | 由 curl_multi_init() 返回的cURL多句柄 |
resource | 是 |
| ch | 由 curl_init() 返回的cURL句柄 |
resource | 是 |
成功时返回 0 (CURLM_OK),失败时返回 1 (CURLM_BAD_HANDLE) 或其他错误代码。
@php
// 创建cURL多句柄
$multiHandle = curl_multi_init();
// 创建单个cURL句柄
$ch1 = curl_init();
curl_setopt_array($ch1, [
CURLOPT_URL => "https://httpbin.org/get",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
// 将单个句柄添加到多句柄
$result = curl_multi_add_handle($multiHandle, $ch1);
if ($result === CURLM_OK) {
echo "成功将cURL句柄添加到多句柄\n";
} else {
echo "添加cURL句柄失败,错误代码: " . $result . "\n";
exit;
}
// 执行多句柄请求
$running = null;
do {
curl_multi_exec($multiHandle, $running);
curl_multi_select($multiHandle, 0.1);
} while ($running > 0);
// 获取结果
$content = curl_multi_getcontent($ch1);
echo "请求完成,内容长度: " . strlen($content) . " 字节\n";
// 清理资源
curl_multi_remove_handle($multiHandle, $ch1);
curl_close($ch1);
curl_multi_close($multiHandle);
@endphp
@php
// 初始化多句柄
$mh = curl_multi_init();
// URL列表
$urls = [
'https://httpbin.org/json',
'https://httpbin.org/xml',
'https://httpbin.org/headers',
'https://httpbin.org/uuid'
];
$handles = [];
$addedCount = 0;
echo "开始添加 " . count($urls) . " 个cURL句柄到多句柄...\n";
// 为每个URL创建并添加句柄
foreach ($urls as $index => $url) {
// 创建单个cURL句柄
$handles[$index] = curl_init();
// 设置选项
curl_setopt_array($handles[$index], [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_USERAGENT => 'ParallelRequest/1.0'
]);
// 添加到多句柄
$addResult = curl_multi_add_handle($mh, $handles[$index]);
if ($addResult === CURLM_OK) {
$addedCount++;
echo "✓ 成功添加句柄 {$index}: {$url}\n";
} else {
echo "✗ 添加句柄 {$index} 失败: {$url} (错误代码: {$addResult})\n";
}
}
echo "\n总共成功添加 {$addedCount}/" . count($urls) . " 个句柄\n";
if ($addedCount > 0) {
echo "\n开始执行并行请求...\n";
// 执行所有请求
$running = null;
do {
$status = curl_multi_exec($mh, $running);
if ($running) {
curl_multi_select($mh, 0.1);
}
} while ($running > 0 && $status === CURLM_OK);
// 处理结果
$successCount = 0;
foreach ($handles as $index => $handle) {
$content = curl_multi_getcontent($handle);
$info = curl_getinfo($handle);
if ($content !== false && $info['http_code'] === 200) {
$successCount++;
echo "✓ 请求 {$index} 成功: HTTP {$info['http_code']}, 长度: " . strlen($content) . " 字节\n";
} else {
$error = curl_error($handle);
echo "✗ 请求 {$index} 失败: {$error}\n";
}
// 移除句柄
curl_multi_remove_handle($mh, $handle);
curl_close($handle);
}
echo "\n请求完成: {$successCount}/{$addedCount} 成功\n";
}
// 关闭多句柄
curl_multi_close($mh);
@endphp
@php
class MultiHandleManager {
private $multiHandle;
private $activeHandles = [];
public function __construct() {
$this->multiHandle = curl_multi_init();
if ($this->multiHandle === false) {
throw new Exception('无法初始化cURL多句柄');
}
}
/**
* 添加cURL句柄到多句柄
* @param resource $ch cURL句柄
* @param string $identifier 句柄标识符
* @return bool 是否添加成功
*/
public function addHandle($ch, $identifier = null) {
// 验证参数
if (!is_resource($ch) || get_resource_type($ch) !== 'curl') {
throw new InvalidArgumentException('参数必须是有效的cURL句柄资源');
}
if (!is_resource($this->multiHandle)) {
throw new Exception('多句柄未初始化或已关闭');
}
// 生成标识符
if ($identifier === null) {
$identifier = 'handle_' . count($this->activeHandles);
}
// 检查是否已存在相同标识符
if (isset($this->activeHandles[$identifier])) {
throw new InvalidArgumentException("标识符 '{$identifier}' 已存在");
}
// 添加句柄到多句柄
$result = curl_multi_add_handle($this->multiHandle, $ch);
if ($result === CURLM_OK) {
$this->activeHandles[$identifier] = $ch;
echo "✓ 成功添加句柄: {$identifier}\n";
return true;
} else {
$errorMsg = $this->getMultiErrorDescription($result);
echo "✗ 添加句柄失败: {$identifier} ({$errorMsg})\n";
return false;
}
}
/**
* 批量添加多个句柄
*/
public function addMultipleHandles($handles) {
$results = [];
foreach ($handles as $identifier => $ch) {
try {
$success = $this->addHandle($ch, $identifier);
$results[$identifier] = $success;
} catch (Exception $e) {
$results[$identifier] = false;
echo "添加句柄 {$identifier} 时出错: " . $e->getMessage() . "\n";
}
}
return $results;
}
/**
* 执行所有请求
*/
public function executeAll() {
if (empty($this->activeHandles)) {
echo "没有活动的句柄需要执行\n";
return [];
}
echo "开始执行 " . count($this->activeHandles) . " 个并行请求...\n";
$running = null;
$startTime = microtime(true);
do {
$status = curl_multi_exec($this->multiHandle, $running);
if ($running) {
// 等待活动
curl_multi_select($this->multiHandle, 0.1);
}
// 处理错误状态
if ($status !== CURLM_OK) {
$errorMsg = $this->getMultiErrorDescription($status);
echo "多句柄执行错误: {$errorMsg}\n";
break;
}
} while ($running > 0);
$totalTime = microtime(true) - $startTime;
echo "所有请求完成,总耗时: " . round($totalTime, 3) . " 秒\n";
return $this->collectResults();
}
/**
* 收集所有请求结果
*/
private function collectResults() {
$results = [];
foreach ($this->activeHandles as $identifier => $ch) {
$content = curl_multi_getcontent($ch);
$info = curl_getinfo($ch);
$error = curl_error($ch);
$errno = curl_errno($ch);
$results[$identifier] = [
'success' => ($errno === 0 && $content !== false),
'content' => $content,
'info' => $info,
'error' => $error,
'errno' => $errno,
'content_length' => strlen($content)
];
// 从多句柄中移除
curl_multi_remove_handle($this->multiHandle, $ch);
}
return $results;
}
/**
* 获取错误代码描述
*/
private function getMultiErrorDescription($errorCode) {
$descriptions = [
CURLM_OK => '操作成功',
CURLM_BAD_HANDLE => '无效的多句柄',
CURLM_BAD_EASY_HANDLE => '无效的简单句柄',
CURLM_OUT_OF_MEMORY => '内存不足',
CURLM_INTERNAL_ERROR => '内部错误',
CURLM_BAD_SOCKET => '无效的socket',
CURLM_UNKNOWN_OPTION => '未知选项',
CURLM_ADDED_ALREADY => '句柄已添加'
];
return $descriptions[$errorCode] ?? "未知错误代码: {$errorCode}";
}
/**
* 清理资源
*/
public function close() {
// 关闭所有单个句柄
foreach ($this->activeHandles as $ch) {
if (is_resource($ch)) {
curl_multi_remove_handle($this->multiHandle, $ch);
curl_close($ch);
}
}
// 关闭多句柄
if (is_resource($this->multiHandle)) {
curl_multi_close($this->multiHandle);
}
$this->activeHandles = [];
echo "所有资源已清理\n";
}
public function __destruct() {
$this->close();
}
}
// 使用MultiHandleManager
try {
$manager = new MultiHandleManager();
// 创建多个cURL句柄
$handles = [];
$handles['api_json'] = curl_init('https://httpbin.org/json');
curl_setopt($handles['api_json'], CURLOPT_RETURNTRANSFER, true);
$handles['api_xml'] = curl_init('https://httpbin.org/xml');
curl_setopt($handles['api_xml'], CURLOPT_RETURNTRANSFER, true);
$handles['api_headers'] = curl_init('https://httpbin.org/headers');
curl_setopt($handles['api_headers'], CURLOPT_RETURNTRANSFER, true);
// 添加句柄到多句柄
$addResults = $manager->addMultipleHandles($handles);
echo "\n添加结果统计:\n";
$successCount = count(array_filter($addResults));
$totalCount = count($addResults);
echo "成功: {$successCount}/{$totalCount}\n";
// 执行所有请求
if ($successCount > 0) {
$results = $manager->executeAll();
echo "\n请求结果:\n";
foreach ($results as $identifier => $result) {
$status = $result['success'] ? '✓ 成功' : '✗ 失败';
$size = $result['content_length'] . ' 字节';
$time = round($result['info']['total_time'], 3) . ' 秒';
echo "{$identifier}: {$status} | 大小: {$size} | 耗时: {$time}\n";
}
}
// 显式关闭(可选,析构函数也会处理)
$manager->close();
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
@endphp
@php
class DynamicMultiRequest {
private $multiHandle;
private $activeHandles = [];
private $completedHandles = [];
private $maxConcurrent;
public function __construct($maxConcurrent = 5) {
$this->multiHandle = curl_multi_init();
$this->maxConcurrent = $maxConcurrent;
}
/**
* 动态添加请求
*/
public function addRequest($url, $options = [], $id = null) {
if ($id === null) {
$id = uniqid('req_');
}
$ch = curl_init();
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_NOPROGRESS => false,
CURLOPT_PROGRESSFUNCTION => function($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($id) {
if ($download_size > 0) {
$progress = round(($downloaded / $download_size) * 100);
// 在实际应用中,这里可以更新进度显示
echo "进度 [{$id}]: {$progress}%\r";
}
return 0;
}
];
$finalOptions = $options + $defaultOptions;
curl_setopt_array($ch, $finalOptions);
// 添加到多句柄
$result = curl_multi_add_handle($this->multiHandle, $ch);
if ($result === CURLM_OK) {
$this->activeHandles[$id] = [
'handle' => $ch,
'url' => $url,
'added_time' => microtime(true)
];
return $id;
} else {
curl_close($ch);
return false;
}
}
/**
* 执行所有请求(支持动态添加)
*/
public function execute($pendingUrls = []) {
$allUrls = $pendingUrls;
$processedCount = 0;
echo "开始执行动态并行请求 (最大并发: {$this->maxConcurrent})...\n\n";
$startTime = microtime(true);
while (!empty($this->activeHandles) || !empty($allUrls)) {
// 动态添加新请求(不超过最大并发数)
while (count($this->activeHandles) < $this->maxConcurrent && !empty($allUrls)) {
$url = array_shift($allUrls);
$id = $this->addRequest($url);
if ($id) {
$processedCount++;
echo "已添加请求: {$url} [{$processedCount}]\n";
}
}
if (empty($this->activeHandles)) {
break;
}
// 执行当前活动的请求
$this->executeActiveRequests();
// 处理完成的请求
$this->processCompletedRequests();
}
$totalTime = microtime(true) - $startTime;
echo "\n所有请求完成!\n";
echo "总请求数: {$processedCount}\n";
echo "总耗时: " . round($totalTime, 2) . " 秒\n";
return $this->completedHandles;
}
private function executeActiveRequests() {
$running = null;
do {
$status = curl_multi_exec($this->multiHandle, $running);
if ($running) {
curl_multi_select($this->multiHandle, 0.1);
}
} while ($running > 0 && $status === CURLM_OK);
}
private function processCompletedRequests() {
while ($info = curl_multi_info_read($this->multiHandle)) {
$handle = $info['handle'];
$id = $this->findHandleId($handle);
if ($id !== null) {
$this->completeRequest($id, $handle, $info);
}
}
}
private function findHandleId($handle) {
foreach ($this->activeHandles as $id => $data) {
if ($data['handle'] === $handle) {
return $id;
}
}
return null;
}
private function completeRequest($id, $handle, $info) {
$content = curl_multi_getcontent($handle);
$curlInfo = curl_getinfo($handle);
$error = curl_error($handle);
$requestData = $this->activeHandles[$id];
$processingTime = microtime(true) - $requestData['added_time'];
$this->completedHandles[$id] = [
'url' => $requestData['url'],
'success' => ($info['result'] === CURLE_OK),
'http_code' => $curlInfo['http_code'],
'processing_time' => $processingTime,
'content_length' => strlen($content),
'error' => $error
];
// 从多句柄中移除
curl_multi_remove_handle($this->multiHandle, $handle);
curl_close($handle);
unset($this->activeHandles[$id]);
$status = $info['result'] === CURLE_OK ? '完成' : '失败';
echo "请求完成 [{$id}]: {$status} (" . round($processingTime, 2) . "秒)\n";
}
public function close() {
foreach ($this->activeHandles as $data) {
if (is_resource($data['handle'])) {
curl_multi_remove_handle($this->multiHandle, $data['handle']);
curl_close($data['handle']);
}
}
if (is_resource($this->multiHandle)) {
curl_multi_close($this->multiHandle);
}
$this->activeHandles = [];
$this->completedHandles = [];
}
public function __destruct() {
$this->close();
}
}
// 使用动态并行请求
$dynamic = new DynamicMultiRequest(3); // 最大并发3个
// 准备URL列表(模拟20个请求)
$urls = [];
for ($i = 1; $i <= 20; $i++) {
$delay = ($i % 4) + 1; // 延迟1-4秒
$urls[] = "https://httpbin.org/delay/{$delay}";
}
echo "准备处理 " . count($urls) . " 个URL...\n";
$results = $dynamic->execute($urls);
// 统计结果
$successful = array_filter($results, function($r) {
return $r['success'];
});
echo "\n最终统计:\n";
echo "成功: " . count($successful) . " 个\n";
echo "失败: " . (count($results) - count($successful)) . " 个\n";
// 显示最快的3个请求
usort($results, function($a, $b) {
return $a['processing_time'] <=> $b['processing_time'];
});
echo "\n最快的3个请求:\n";
for ($i = 0; $i < min(3, count($results)); $i++) {
$result = $results[$i];
echo ($i + 1) . ". " . $result['url'] . " (" . round($result['processing_time'], 2) . "秒)\n";
}
$dynamic->close();
@endphp
| 错误情况 | 错误描述 | 解决方案 |
|---|---|---|
| 无效的多句柄 | 传递的多句柄参数不是有效的资源 | 确保使用curl_multi_init()正确初始化多句柄,并检查资源是否有效 |
| 无效的cURL句柄 | 传递的cURL句柄参数不是有效的资源 | 确保使用curl_init()正确初始化cURL句柄,并检查资源类型 |
| 句柄已添加 | 同一个cURL句柄被多次添加到多句柄 | 每个cURL句柄只能添加到一个多句柄一次,使用唯一标识符跟踪 |
| 内存不足 | 系统内存不足,无法添加新句柄 | 减少并发数量,优化内存使用,或增加系统内存 |
| 句柄已执行 | 尝试添加已经执行过的cURL句柄 | 为每个请求创建新的cURL句柄,不要重用已执行的句柄 |
在添加句柄前,确保多句柄和cURL句柄都是有效的资源。
为每个句柄分配唯一标识符,便于后续管理和错误追踪。
始终检查curl_multi_add_handle()的返回值,处理可能的错误。
确保在请求完成后正确移除和关闭所有句柄,防止资源泄漏。