在使用 curl_multi_* 系列函数进行并发请求时,我们通常会开启多个 curl 句柄并将它们加入到一个 curl_multi 句柄中统一管理。
为了确保在调用 curl_multi_close 释放资源之前,所有的请求都已经正确完成,我们需要合理地检测每一个请求的执行状态。本文将讲解一种标准且可靠的方法来实现这一需求。
通常我们会按照以下步骤来处理多个请求:
初始化多个 curl 单独句柄。
将这些句柄添加到一个 curl_multi 句柄中。
使用 curl_multi_exec 不断地执行请求。
使用 curl_multi_select 配合 curl_multi_exec 检测是否有请求完成。
逐个移除已完成的请求句柄。
确保所有请求完成后,再调用 curl_multi_close。
下面是一个完整示例,假设我们需要请求多个 URL(这里以 https://gitbox.net/example1 和 https://gitbox.net/example2 举例):
<?php
// 初始化 cURL multi 句柄
$multiHandle = curl_multi_init();
// 存放各个 cURL 句柄的数组
$curlHandles = [];
// 要请求的 URL 列表
$urls = [
'https://gitbox.net/example1',
'https://gitbox.net/example2',
];
// 创建并添加每一个 cURL 句柄
foreach ($urls as $i => $url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($multiHandle, $ch);
$curlHandles[$i] = $ch;
}
// 执行 multi 请求
$running = null;
do {
$status = curl_multi_exec($multiHandle, $running);
// 防止 CPU 空转
if ($status == CURLM_OK) {
curl_multi_select($multiHandle);
} else {
break;
}
// 检查是否有已完成的请求
while ($info = curl_multi_info_read($multiHandle)) {
if ($info['msg'] == CURLMSG_DONE) {
// 获取完成的句柄
$completedHandle = $info['handle'];
// 获取返回内容
$response = curl_multi_getcontent($completedHandle);
// 可在这里处理返回的数据
echo "请求完成,返回数据长度:" . strlen($response) . PHP_EOL;
// 移除并关闭已完成的句柄
curl_multi_remove_handle($multiHandle, $completedHandle);
curl_close($completedHandle);
}
}
} while ($running > 0);
// 最后关闭 multi 句柄
curl_multi_close($multiHandle);
curl_multi_exec:推进并发请求的执行,返回当前仍在运行的连接数量。
curl_multi_select:用于阻塞等待活动连接,减少 CPU 资源浪费。
curl_multi_info_read:检测是否有请求完成,并可以一次性处理已完成的句柄。
只有当 $running == 0,并且所有子句柄都已经被移除并关闭后,才调用 curl_multi_close。
如果请求很多,建议添加超时机制,以防某些请求长时间卡住。
curl_multi_select 有时返回 -1,在这种情况下需要使用 usleep() 进行小幅度等待,防止死循环。
if (curl_multi_select($multiHandle) == -1) {
usleep(100);
}
这样可以保证脚本更加健壮。
在使用 curl_multi_close 之前,关键是确认所有子请求都已经完成并正确清理,以上的模式是通用且推荐的标准做法。
多了解 curl_multi_* 系列的行为,有助于写出更高效、稳定的并发网络请求代码!