PHP curl_multi_info_read函数

定义和用法

curl_multi_info_read() 函数用于从cURL多句柄中读取已完成传输的信息。这个函数是并行请求处理中的关键函数,它允许程序获取每个请求的完成状态、结果代码和对应的句柄。

提示: curl_multi_info_read() 会从信息队列中读取一条信息并将其移除,因此需要循环调用直到返回 FALSE 来表示所有信息都已读取。

语法

array|false curl_multi_info_read ( resource $mh [, int &$msgs_in_queue = null ] )

参数

参数 描述 类型 必需
mh curl_multi_init() 返回的cURL多句柄 resource
msgs_in_queue 引用参数,返回仍然在信息队列中的消息数量 int

返回值

返回一个关联数组,包含已完成请求的信息,或者在没有更多信息时返回 FALSE

返回数组包含以下键:

  • msg - 常量 CURLMSG_DONE,表示传输已完成
  • result - cURL错误代码,CURLE_OK 表示成功
  • handle - 完成传输的cURL句柄资源

信息读取工作流程

1
curl_multi_exec()
执行多句柄
2
检查完成请求
循环调用
3
curl_multi_info_read()
读取完成信息
4
处理结果
获取内容与清理

信息数组结构

array(
    'msg' => CURLMSG_DONE,    // 固定值,表示传输完成
    'result' => CURLE_OK,     // cURL错误代码,0表示成功
    'handle' => resource      // 对应的cURL句柄资源
)

通过 result 字段可以判断请求是否成功:

  • CURLE_OK (0) - 请求成功完成
  • 其他值 - 请求失败,具体错误代码可参考 curl_errno() 文档

示例

示例 1:基本用法 - 读取完成信息

@php
// 创建多句柄
$mh = curl_multi_init();

// 创建多个cURL句柄
$urls = [
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/2',
    'https://httpbin.org/delay/3'
];

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

// 执行并行请求
$active = null;
do {
    $status = curl_multi_exec($mh, $active);
    if ($active) {
        curl_multi_select($mh, 0.1);
    }
} while ($active > 0);

echo "所有请求执行完成,开始读取完成信息...\n\n";

// 读取所有完成信息
$completedCount = 0;
$msgsInQueue = null;

while ($info = curl_multi_info_read($mh, $msgsInQueue)) {
    $completedCount++;

    echo "=== 完成信息 #{$completedCount} ===\n";
    echo "消息类型: {$info['msg']} " . ($info['msg'] === CURLMSG_DONE ? '(CURLMSG_DONE)' : '') . "\n";
    echo "结果代码: {$info['result']} " . ($info['result'] === CURLE_OK ? '(CURLE_OK - 成功)' : '(失败)') . "\n";

    // 查找对应的URL
    $handle = $info['handle'];
    $index = array_search($handle, $handles, true);
    $url = $index !== false ? $urls[$index] : '未知URL';

    echo "对应URL: {$url}\n";

    // 获取更多信息
    $content = curl_multi_getcontent($handle);
    $curlInfo = curl_getinfo($handle);

    echo "HTTP状态码: {$curlInfo['http_code']}\n";
    echo "总耗时: " . round($curlInfo['total_time'], 3) . " 秒\n";
    echo "内容长度: " . strlen($content) . " 字节\n";

    if ($info['result'] !== CURLE_OK) {
        $error = curl_error($handle);
        echo "错误信息: {$error}\n";
    }

    echo "剩余队列消息: {$msgsInQueue}\n\n";

    // 清理资源
    curl_multi_remove_handle($mh, $handle);
    curl_close($handle);
}

echo "总共读取了 {$completedCount} 条完成信息\n";

curl_multi_close($mh);
@endphp

示例 2:实时处理完成请求

@php
class RealTimeInfoProcessor {
    private $multiHandle;
    private $handles = [];
    private $results = [];

    public function __construct() {
        $this->multiHandle = curl_multi_init();
    }

    public function addRequest($url, $id = null) {
        if ($id === null) {
            $id = uniqid('req_');
        }

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30
        ]);

        $this->handles[$id] = $ch;
        curl_multi_add_handle($this->multiHandle, $ch);

        return $id;
    }

    public function execute() {
        echo "开始执行并行请求...\n";

        $active = null;
        $processedCount = 0;

        do {
            // 执行多句柄
            $status = curl_multi_exec($this->multiHandle, $active);

            // 实时处理完成信息
            $this->processCompletedInfos();

            // 显示进度
            $remaining = count($this->handles);
            if ($remaining > 0) {
                echo "进度: {$processedCount} 完成, {$remaining} 进行中, 活动句柄: {$active}\n";
            }

            // 等待活动
            if ($active > 0) {
                curl_multi_select($this->multiHandle, 0.1);
            }

        } while ($active > 0 || !empty($this->handles));

        echo "所有请求处理完成!\n";
        return $this->results;
    }

    private function processCompletedInfos() {
        $msgsInQueue = null;

        // 循环读取所有可用的完成信息
        while ($info = curl_multi_info_read($this->multiHandle, $msgsInQueue)) {
            $this->processSingleInfo($info);
        }
    }

    private function processSingleInfo($info) {
        $handle = $info['handle'];
        $id = array_search($handle, $this->handles, true);

        if ($id === false) {
            echo "警告: 找不到对应的句柄ID\n";
            return;
        }

        // 获取请求结果
        $content = curl_multi_getcontent($handle);
        $curlInfo = curl_getinfo($handle);
        $error = curl_error($handle);

        $result = [
            'success' => ($info['result'] === CURLE_OK),
            'result_code' => $info['result'],
            'content' => $content,
            'info' => $curlInfo,
            'error' => $error,
            'processed_at' => microtime(true)
        ];

        // 根据结果代码进行详细处理
        $this->handleByResultCode($id, $result, $info['result']);

        // 存储结果
        $this->results[$id] = $result;

        // 清理资源
        curl_multi_remove_handle($this->multiHandle, $handle);
        curl_close($handle);
        unset($this->handles[$id]);

        $status = $result['success'] ? '成功' : '失败';
        echo "→ 请求 {$id} 处理完成: {$status} (" . round($curlInfo['total_time'], 2) . "秒)\n";
    }

    private function handleByResultCode($id, &$result, $resultCode) {
        $result['result_description'] = $this->getResultDescription($resultCode);

        switch ($resultCode) {
            case CURLE_OK:
                // 成功请求
                $result['action'] = 'process_content';
                break;

            case CURLE_OPERATION_TIMEDOUT:
                // 超时请求
                $result['action'] = 'retry_or_fail';
                $result['suggestion'] = '增加超时时间或检查网络连接';
                break;

            case CURLE_COULDNT_RESOLVE_HOST:
                // DNS解析失败
                $result['action'] = 'check_url';
                $result['suggestion'] = '检查URL是否正确,DNS设置';
                break;

            case CURLE_COULDNT_CONNECT:
                // 连接失败
                $result['action'] = 'retry_later';
                $result['suggestion'] = '服务器可能暂时不可用,稍后重试';
                break;

            default:
                $result['action'] = 'investigate';
                $result['suggestion'] = '查看cURL文档了解错误代码含义';
        }
    }

    private function getResultDescription($resultCode) {
        $descriptions = [
            CURLE_OK => '操作成功完成',
            CURLE_OPERATION_TIMEDOUT => '操作超时',
            CURLE_COULDNT_RESOLVE_HOST => '无法解析主机',
            CURLE_COULDNT_CONNECT => '无法连接到服务器',
            CURLE_SSL_CONNECT_ERROR => 'SSL连接错误',
            CURLE_GOT_NOTHING => '服务器未返回任何数据'
        ];

        return $descriptions[$resultCode] ?? "未知错误代码: {$resultCode}";
    }

    public function getStats() {
        $stats = [
            'total_requests' => count($this->results),
            'successful' => 0,
            'failed' => 0,
            'total_time' => 0
        ];

        foreach ($this->results as $result) {
            if ($result['success']) {
                $stats['successful']++;
            } else {
                $stats['failed']++;
            }
            $stats['total_time'] += $result['info']['total_time'];
        }

        return $stats;
    }

    public function close() {
        foreach ($this->handles as $handle) {
            if (is_resource($handle)) {
                curl_multi_remove_handle($this->multiHandle, $handle);
                curl_close($handle);
            }
        }

        if (is_resource($this->multiHandle)) {
            curl_multi_close($this->multiHandle);
        }

        $this->handles = [];
        $this->results = [];
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用实时信息处理器
$processor = new RealTimeInfoProcessor();

// 添加多个请求(包含一些可能失败的URL用于测试)
$requests = [
    'fast' => 'https://httpbin.org/delay/1',
    'medium' => 'https://httpbin.org/delay/2',
    'slow' => 'https://httpbin.org/delay/3',
    'timeout' => 'https://httpbin.org/delay/5', // 可能超时
    'invalid' => 'https://invalid-url-test-12345.com' // 会失败
];

foreach ($requests as $id => $url) {
    $processor->addRequest($url, $id);
}

// 执行并实时处理
$results = $processor->execute();

// 显示统计信息
$stats = $processor->getStats();
echo "\n执行统计:\n";
echo "总请求数: {$stats['total_requests']}\n";
echo "成功: {$stats['successful']}\n";
echo "失败: {$stats['failed']}\n";
echo "成功率: " . round(($stats['successful'] / $stats['total_requests']) * 100, 1) . "%\n";
echo "总耗时: " . round($stats['total_time'], 2) . " 秒\n";

// 显示详细结果
echo "\n详细结果:\n";
foreach ($results as $id => $result) {
    $status = $result['success'] ? '✓' : '✗';
    $code = $result['result_code'];
    $desc = $result['result_description'];
    echo "{$status} {$id}: 代码{$code} - {$desc}\n";
    if (!$result['success']) {
        echo "  建议: {$result['suggestion']}\n";
    }
}

$processor->close();
@endphp

示例 3:基于完成信息的重试机制

@php
class RetryableMultiRequest {
    private $multiHandle;
    private $requests = [];
    private $maxRetries;
    private $completed = [];

    public function __construct($maxRetries = 3) {
        $this->multiHandle = curl_multi_init();
        $this->maxRetries = $maxRetries;
    }

    public function addRequest($url, $id = null) {
        if ($id === null) {
            $id = uniqid('req_');
        }

        $this->requests[$id] = [
            'url' => $url,
            'handle' => null,
            'retries' => 0,
            'completed' => false,
            'last_result' => null
        ];

        $this->createHandle($id);

        return $id;
    }

    private function createHandle($id) {
        $request = &$this->requests[$id];

        // 清理旧的句柄(如果存在)
        if ($request['handle'] && is_resource($request['handle'])) {
            curl_multi_remove_handle($this->multiHandle, $request['handle']);
            curl_close($request['handle']);
        }

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $request['url'],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 10,
            CURLOPT_CONNECTTIMEOUT => 5
        ]);

        $request['handle'] = $ch;
        $request['retries']++;

        curl_multi_add_handle($this->multiHandle, $ch);

        echo "创建句柄: {$id} (尝试: {$request['retries']}/{$this->maxRetries})\n";
    }

    public function execute() {
        echo "开始执行带重试机制的并行请求...\n";
        echo "最大重试次数: {$this->maxRetries}\n\n";

        $startTime = microtime(true);

        do {
            // 执行多句柄
            $active = null;
            do {
                curl_multi_exec($this->multiHandle, $active);
                if ($active > 0) {
                    curl_multi_select($this->multiHandle, 0.1);
                }
            } while ($active > 0);

            // 处理所有完成信息
            $this->processCompletedRequests();

            // 重试失败的请求
            $retryCount = $this->retryFailedRequests();

            if ($retryCount > 0) {
                echo "正在进行第 {$retryCount} 轮重试...\n";
            }

        } while ($retryCount > 0 && !empty($this->requests));

        $totalTime = microtime(true) - $startTime;
        echo "\n所有请求处理完成!总耗时: " . round($totalTime, 2) . " 秒\n";

        return $this->completed;
    }

    private function processCompletedRequests() {
        $msgsInQueue = null;

        while ($info = curl_multi_info_read($this->multiHandle, $msgsInQueue)) {
            $handle = $info['handle'];
            $id = $this->findRequestId($handle);

            if ($id !== null) {
                $this->handleRequestCompletion($id, $handle, $info);
            }
        }
    }

    private function findRequestId($handle) {
        foreach ($this->requests as $id => $request) {
            if ($request['handle'] === $handle) {
                return $id;
            }
        }
        return null;
    }

    private function handleRequestCompletion($id, $handle, $info) {
        $request = &$this->requests[$id];

        $content = curl_multi_getcontent($handle);
        $curlInfo = curl_getinfo($handle);
        $error = curl_error($handle);

        $result = [
            'success' => ($info['result'] === CURLE_OK),
            'result_code' => $info['result'],
            'content' => $content,
            'info' => $curlInfo,
            'error' => $error,
            'retries' => $request['retries'] - 1,
            'final_attempt' => ($request['retries'] >= $this->maxRetries)
        ];

        $request['last_result'] = $result;

        // 从多句柄中移除当前句柄
        curl_multi_remove_handle($this->multiHandle, $handle);
        curl_close($handle);

        $status = $result['success'] ? '成功' : '失败';
        echo "请求 {$id}: {$status} (尝试: {$request['retries']}, 代码: {$info['result']})\n";

        // 如果成功或达到最大重试次数,标记为完成
        if ($result['success'] || $request['retries'] >= $this->maxRetries) {
            $this->completed[$id] = $result;
            unset($this->requests[$id]);
        }
    }

    private function retryFailedRequests() {
        $retryCount = 0;
        $requestsToRetry = [];

        // 收集需要重试的请求
        foreach ($this->requests as $id => $request) {
            if (!$request['completed'] && $request['retries'] < $this->maxRetries) {
                $requestsToRetry[] = $id;
            }
        }

        // 重试请求
        foreach ($requestsToRetry as $id) {
            $this->createHandle($id);
            $retryCount++;
        }

        return $retryCount;
    }

    public function getStats() {
        $stats = [
            'total' => count($this->completed),
            'successful' => 0,
            'failed' => 0,
            'total_retries' => 0
        ];

        foreach ($this->completed as $result) {
            if ($result['success']) {
                $stats['successful']++;
            } else {
                $stats['failed']++;
            }
            $stats['total_retries'] += $result['retries'];
        }

        return $stats;
    }

    public function close() {
        foreach ($this->requests as $request) {
            if ($request['handle'] && is_resource($request['handle'])) {
                curl_multi_remove_handle($this->multiHandle, $request['handle']);
                curl_close($request['handle']);
            }
        }

        if (is_resource($this->multiHandle)) {
            curl_multi_close($this->multiHandle);
        }

        $this->requests = [];
        $this->completed = [];
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用带重试机制的请求器
$requester = new RetryableMultiRequest(2); // 最大重试2次

// 添加请求(包含一些可能失败的URL)
$urls = [
    'reliable' => 'https://httpbin.org/delay/1',
    'slow' => 'https://httpbin.org/delay/3',
    'unreliable' => 'https://httpbin.org/delay/5', // 可能超时
    'invalid' => 'https://invalid-domain-12345.net' // 会失败
];

foreach ($urls as $id => $url) {
    $requester->addRequest($url, $id);
}

$results = $requester->execute();

// 显示统计信息
$stats = $requester->getStats();
echo "\n最终统计:\n";
echo "总请求: {$stats['total']}\n";
echo "成功: {$stats['successful']}\n";
echo "失败: {$stats['failed']}\n";
echo "总重试次数: {$stats['total_retries']}\n";

// 显示每个请求的详细结果
echo "\n详细结果:\n";
foreach ($results as $id => $result) {
    $status = $result['success'] ? '✓' : '✗';
    $attempts = $result['retries'] + 1;
    $final = $result['final_attempt'] ? ' (最终尝试)' : '';

    echo "{$status} {$id}: 尝试{$attempts}次";

    if ($result['success']) {
        echo ", 内容长度: " . strlen($result['content']) . " 字节";
    } else {
        echo ", 错误: {$result['error']}";
    }

    echo "{$final}\n";
}

$requester->close();
@endphp

示例 4:高级监控与性能分析

@php
class AdvancedMultiMonitor {
    private $multiHandle;
    private $requests = [];
    private $results = [];
    private $startTime;
    private $monitoringData = [];

    public function __construct() {
        $this->multiHandle = curl_multi_init();
        $this->startTime = microtime(true);
    }

    public function addRequest($url, $id = null, $metadata = []) {
        if ($id === null) {
            $id = uniqid('req_');
        }

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HEADER => true, // 包含头部信息用于分析
            CURLOPT_NOPROGRESS => false,
            CURLOPT_PROGRESSFUNCTION => function($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($id) {
                $this->recordProgress($id, $download_size, $downloaded);
                return 0;
            }
        ]);

        $this->requests[$id] = [
            'handle' => $ch,
            'url' => $url,
            'metadata' => $metadata,
            'start_time' => microtime(true),
            'progress' => []
        ];

        curl_multi_add_handle($this->multiHandle, $ch);

        return $id;
    }

    private function recordProgress($id, $totalSize, $downloaded) {
        if ($totalSize > 0) {
            $progress = round(($downloaded / $totalSize) * 100, 1);
            $this->requests[$id]['progress'][] = [
                'time' => microtime(true) - $this->startTime,
                'progress' => $progress,
                'downloaded' => $downloaded,
                'total_size' => $totalSize
            ];
        }
    }

    public function execute() {
        echo "开始高级监控执行...\n";

        $active = null;
        $lastMonitorTime = 0;

        do {
            // 执行多句柄
            $status = curl_multi_exec($this->multiHandle, $active);

            // 处理完成信息
            $this->monitorCompletedRequests();

            // 定期输出监控信息(每秒一次)
            $currentTime = microtime(true);
            if ($currentTime - $lastMonitorTime >= 1.0) {
                $this->outputMonitoringInfo();
                $lastMonitorTime = $currentTime;
            }

            // 等待活动
            if ($active > 0) {
                curl_multi_select($this->multiHandle, 0.1);
            }

        } while ($active > 0 || !empty($this->requests));

        // 最终处理
        $this->monitorCompletedRequests(true);

        $totalTime = microtime(true) - $this->startTime;
        echo "\n执行完成!总耗时: " . round($totalTime, 2) . " 秒\n";

        return $this->generateReport();
    }

    private function monitorCompletedRequests($final = false) {
        $msgsInQueue = null;

        while ($info = curl_multi_info_read($this->multiHandle, $msgsInQueue)) {
            $this->processRequestCompletion($info);
        }
    }

    private function processRequestCompletion($info) {
        $handle = $info['handle'];
        $id = $this->findRequestId($handle);

        if ($id === null) {
            return;
        }

        $request = $this->requests[$id];
        $completionTime = microtime(true);
        $duration = $completionTime - $request['start_time'];

        // 获取完整响应(包含头部)
        $fullResponse = curl_multi_getcontent($handle);
        $curlInfo = curl_getinfo($handle);
        $error = curl_error($handle);

        // 分离头部和主体
        $headerSize = $curlInfo['header_size'];
        $headers = substr($fullResponse, 0, $headerSize);
        $body = substr($fullResponse, $headerSize);

        $result = [
            'success' => ($info['result'] === CURLE_OK),
            'result_code' => $info['result'],
            'headers' => $headers,
            'body' => $body,
            'info' => $curlInfo,
            'error' => $error,
            'duration' => $duration,
            'progress_data' => $request['progress'],
            'metadata' => $request['metadata'],
            'completed_at' => $completionTime
        ];

        // 分析性能数据
        $this->analyzePerformance($id, $result);

        $this->results[$id] = $result;

        // 清理资源
        curl_multi_remove_handle($this->multiHandle, $handle);
        curl_close($handle);
        unset($this->requests[$id]);

        $this->recordCompletion($id, $result);
    }

    private function findRequestId($handle) {
        foreach ($this->requests as $id => $request) {
            if ($request['handle'] === $handle) {
                return $id;
            }
        }
        return null;
    }

    private function analyzePerformance($id, &$result) {
        $progress = $result['progress_data'];

        if (count($progress) > 1) {
            // 计算平均下载速度
            $first = $progress[0];
            $last = end($progress);

            $timeDiff = $last['time'] - $first['time'];
            $dataDiff = $last['downloaded'] - $first['downloaded'];

            if ($timeDiff > 0) {
                $result['avg_speed_kbps'] = round(($dataDiff / $timeDiff) / 1024, 2);
            }

            // 检测下载阶段
            $result['download_phases'] = $this->detectDownloadPhases($progress);
        }
    }

    private function detectDownloadPhases($progress) {
        if (count($progress) < 3) {
            return ['数据不足分析阶段'];
        }

        $phases = [];
        $lastProgress = 0;

        foreach ($progress as $point) {
            $currentProgress = $point['progress'];

            if ($currentProgress - $lastProgress > 20) {
                $phases[] = "阶段: {$lastProgress}% → {$currentProgress}%";
                $lastProgress = $currentProgress;
            }
        }

        return $phases ?: ['稳定下载'];
    }

    private function outputMonitoringInfo() {
        $elapsed = microtime(true) - $this->startTime;
        $completed = count($this->results);
        $remaining = count($this->requests);
        $active = 0;

        // 计算活动请求数(粗略估计)
        foreach ($this->requests as $request) {
            if (!empty($request['progress'])) {
                $active++;
            }
        }

        echo sprintf(
            "[%.1fs] 完成: %d, 进行中: %d, 活动: %d, 总进度: %.1f%%\n",
            $elapsed, $completed, $remaining, $active,
            $completed > 0 ? ($completed / ($completed + $remaining)) * 100 : 0
        );

        // 显示最近完成的请求
        $recentResults = array_slice($this->results, -3);
        foreach ($recentResults as $id => $result) {
            $status = $result['success'] ? '✓' : '✗';
            $speed = isset($result['avg_speed_kbps']) ? $result['avg_speed_kbps'] . ' KB/s' : 'N/A';
            echo "  {$status} {$id}: " . round($result['duration'], 2) . "s, {$speed}\n";
        }
    }

    private function recordCompletion($id, $result) {
        $this->monitoringData['completions'][] = [
            'id' => $id,
            'time' => microtime(true) - $this->startTime,
            'success' => $result['success'],
            'duration' => $result['duration']
        ];
    }

    private function generateReport() {
        $report = [
            'summary' => $this->generateSummary(),
            'detailed_results' => $this->results,
            'monitoring_data' => $this->monitoringData,
            'performance_analysis' => $this->analyzeOverallPerformance()
        ];

        return $report;
    }

    private function generateSummary() {
        $total = count($this->results);
        $successful = 0;
        $totalDuration = 0;
        $totalData = 0;

        foreach ($this->results as $result) {
            if ($result['success']) {
                $successful++;
                $totalDuration += $result['duration'];
                $totalData += strlen($result['body']);
            }
        }

        return [
            'total_requests' => $total,
            'successful_requests' => $successful,
            'success_rate' => $total > 0 ? round(($successful / $total) * 100, 1) : 0,
            'average_duration' => $successful > 0 ? round($totalDuration / $successful, 3) : 0,
            'total_data_transferred' => $totalData,
            'total_data_mb' => round($totalData / 1024 / 1024, 2)
        ];
    }

    private function analyzeOverallPerformance() {
        if (empty($this->results)) {
            return ['无数据可用于分析'];
        }

        $durations = [];
        foreach ($this->results as $result) {
            if ($result['success']) {
                $durations[] = $result['duration'];
            }
        }

        if (empty($durations)) {
            return ['无成功请求可用于性能分析'];
        }

        sort($durations);

        return [
            'fastest_request' => round(min($durations), 3),
            'slowest_request' => round(max($durations), 3),
            'median_duration' => round($durations[floor(count($durations) / 2)], 3),
            'total_execution_time' => round(microtime(true) - $this->startTime, 2)
        ];
    }

    public function close() {
        foreach ($this->requests as $request) {
            if (is_resource($request['handle'])) {
                curl_multi_remove_handle($this->multiHandle, $request['handle']);
                curl_close($request['handle']);
            }
        }

        if (is_resource($this->multiHandle)) {
            curl_multi_close($this->multiHandle);
        }

        $this->requests = [];
        $this->results = [];
        $this->monitoringData = [];
    }

    public function __destruct() {
        $this->close();
    }
}

// 使用高级监控器
$monitor = new AdvancedMultiMonitor();

// 添加多个请求进行监控
$urls = [
    'fast_api' => 'https://httpbin.org/delay/1',
    'medium_api' => 'https://httpbin.org/delay/2',
    'slow_api' => 'https://httpbin.org/delay/3',
    'json_api' => 'https://httpbin.org/json',
    'xml_api' => 'https://httpbin.org/xml'
];

foreach ($urls as $id => $url) {
    $monitor->addRequest($url, $id, ['type' => 'api_call']);
}

$report = $monitor->execute();

// 显示报告摘要
$summary = $report['summary'];
echo "\n=== 执行报告摘要 ===\n";
echo "总请求数: {$summary['total_requests']}\n";
echo "成功请求: {$summary['successful_requests']}\n";
echo "成功率: {$summary['success_rate']}%\n";
echo "平均耗时: {$summary['average_duration']} 秒\n";
echo "总数据传输: {$summary['total_data_mb']} MB\n";

// 显示性能分析
$performance = $report['performance_analysis'];
echo "\n=== 性能分析 ===\n";
echo "最快请求: {$performance['fastest_request']} 秒\n";
echo "最慢请求: {$performance['slowest_request']} 秒\n";
echo "中位数耗时: {$performance['median_duration']} 秒\n";
echo "总执行时间: {$performance['total_execution_time']} 秒\n";

$monitor->close();
@endphp

常见使用模式

基本循环模式
// 执行完成后读取所有信息
while ($info = curl_multi_info_read($mh)) {
    $handle = $info['handle'];
    if ($info['result'] === CURLE_OK) {
        // 处理成功请求
        $content = curl_multi_getcontent($handle);
    }
    // 清理资源
    curl_multi_remove_handle($mh, $handle);
    curl_close($handle);
}

最简单的使用模式,在所有请求完成后一次性处理。

实时处理模式
// 在执行循环中实时处理
do {
    curl_multi_exec($mh, $active);
    // 实时读取完成信息
    while ($info = curl_multi_info_read($mh)) {
        // 立即处理完成请求
        processCompletedRequest($info);
    }
    if ($active) {
        curl_multi_select($mh, 0.1);
    }
} while ($active > 0);

边执行边处理,适合长时间运行或需要即时响应的场景。

队列监控模式
// 监控队列深度
$msgsInQueue = null;
while ($info = curl_multi_info_read($mh, $msgsInQueue)) {
    processRequest($info);
    echo "剩余队列: {$msgsInQueue}\n";
}

使用 msgs_in_queue 参数监控信息队列深度。

错误处理模式
while ($info = curl_multi_info_read($mh)) {
    if ($info['result'] === CURLE_OK) {
        handleSuccess($info);
    } else {
        handleError($info);
        // 可以根据错误代码决定是否重试
        if (shouldRetry($info['result'])) {
            retryRequest($info['handle']);
        }
    }
}

根据结果代码进行不同的错误处理策略。

最佳实践

  • 循环读取直到返回 FALSE - 确保读取所有完成信息,避免遗漏
  • 及时清理资源 - 读取信息后立即移除句柄并关闭,防止资源泄漏
  • 实时处理 - 在执行循环中实时处理完成请求,提高响应性
  • 检查结果代码 - 根据 result 字段判断请求成功与否
  • 使用队列监控 - 通过 msgs_in_queue 参数了解队列状态
  • 错误分类处理 - 根据不同的错误代码实现不同的处理策略
  • 结合性能监控 - 记录请求时间、进度等数据用于性能分析

常见错误与解决方案

问题现象 可能原因 解决方案
遗漏完成请求 没有循环读取直到返回 FALSE 使用 while 循环确保读取所有信息:while ($info = curl_multi_info_read($mh))
资源泄漏 读取信息后没有清理句柄 及时调用 curl_multi_remove_handle() 和 curl_close()
请求挂起 在 curl_multi_exec() 完成前读取信息 确保在 curl_multi_exec() 返回 active=0 后或在其循环中读取
无法找到对应句柄 句柄标识管理不当 使用关联数组维护句柄与请求的映射关系

注意事项

重要:
  • curl_multi_info_read() 会从信息队列中移除读取的信息,每个信息只能读取一次
  • 函数返回 FALSE 表示当前没有更多信息,但后续可能还有新的完成信息
  • 在 curl_multi_exec() 仍在执行时也可以调用此函数读取已完成请求的信息
  • msgs_in_queue 参数表示调用后剩余的队列消息数,不是总消息数
  • 即使请求失败,也会生成完成信息,需要通过 result 字段判断成功与否
  • 在多线程环境中使用需要注意线程安全问题
  • 及时处理完成信息,避免信息队列积累过多影响性能