在使用 PHP 的 cURL 扩展进行并发请求时,curl_multi_* 系列函数是非常重要的工具。curl_multi_close 负责关闭一个 cURL multi handle,但如果使用不当,很容易导致连接状态异常,比如:部分请求未完成、资源未正确释放、出现莫名其妙的超时等问题。那么,如何才能优雅且正确地避免这些异常呢?本文将详细讲解。
很多开发者在处理并发请求时,代码可能大致像这样:
<?php
$mh = curl_multi_init();
$ch1 = curl_init('https://gitbox.net/api/endpoint1');
$ch2 = curl_init('https://gitbox.net/api/endpoint2');
curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);
// 启动并执行
do {
$status = curl_multi_exec($mh, $active);
curl_multi_select($mh);
} while ($active && $status == CURLM_OK);
// 直接关闭
curl_multi_close($mh);
这种写法的问题在于:即使请求尚未完全处理完毕(尤其是在网络条件差的情况下),curl_multi_close 被直接调用,会导致一些连接被粗暴终止,结果自然就出现了状态异常。
在调用 curl_multi_close 之前,应该确保所有句柄(cURL handle)已经处理完毕,并且移除它们!正确的流程应当是:
反复调用 curl_multi_exec,直到所有请求完成。
移除每一个单独的 cURL 句柄(curl_multi_remove_handle)。
分别关闭单个 cURL 句柄(curl_close)。
最后再关闭 multi handle(curl_multi_close)。
修正后的代码示例如下:
<?php
$mh = curl_multi_init();
$ch1 = curl_init('https://gitbox.net/api/endpoint1');
$ch2 = curl_init('https://gitbox.net/api/endpoint2');
curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);
// 启动并执行
do {
$status = curl_multi_exec($mh, $active);
if ($active) {
curl_multi_select($mh);
}
} while ($active && $status == CURLM_OK);
// 移除并关闭单个 handle
curl_multi_remove_handle($mh, $ch1);
curl_close($ch1);
curl_multi_remove_handle($mh, $ch2);
curl_close($ch2);
// 最后关闭 multi handle
curl_multi_close($mh);
curl_multi_remove_handle 必须在 curl_multi_close 之前进行。
即便是请求失败的连接,也要通过 remove_handle + close 正确清理。
在循环中使用 curl_multi_select 可以减少 CPU 占用,避免 busy waiting。
尽量捕获错误(比如 curl_errno 和 curl_error),不要盲目信任请求一定成功。
在高并发场景下,细节决定成败。curl_multi_close 的本质只是销毁 multi handle,本身不会也不能保证所有单个连接的安全关闭。因此,在关闭 multi handle 前,务必手动 remove 和 close 每一个单独的 handle,这是避免连接状态异常的关键。
遵循正确的处理流程,可以让你的并发请求稳定高效地运行,远离那些令人头疼的隐秘 bug。