前言
使用PHP的curl函数调用多个外部HTTP接口时,默认情况下是需要一个一个执行的,第一个请求结束后才会发起第二个请求,这样会显得效率低。如果各个请求之间没有强依赖关系,可以使用curl的multi系列函数来同时发起多个请求,这样可以节省很多时间,提高效率。
有很多资料说
curl_multi
是使用多线程来实现并发的,但事实上并不是,它仍然是单线程,只是用了系统的select I/O多路复用机制来实现并发。
测试环境
PHP 7.3
代码
<?php
function concurrency_request(array $requests): array
{
$chHandles = [];
/* 根据请求信息创建ch句柄 */
foreach ($requests as $k => $req) {
$options = [
CURLOPT_HEADER => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
];
if (!empty($req['header'])) {
$options[CURLOPT_HTTPHEADER] = $req['header'];
}
switch ($req['method']) {
case 'GET':
$options[CURLOPT_URL] = "{$req['url']}?" . http_build_query($req['param']);
break;
case 'POST':
$options[CURLOPT_URL] = $req['url'];
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = http_build_query($req['param']);
break;
default:
throw new Exception("不支持的HTTP方法:{$req['method']}");
}
$ch = curl_init();
curl_setopt_array($ch, $options);
$chHandles[$k] = $ch;
}
$mh = curl_multi_init();
foreach ($chHandles as $ch) {
curl_multi_add_handle($mh, $ch);
}
/* 不断执行,直至所有请求完成 */
$running = null;
do {
$errCode = curl_multi_exec($mh, $running);
if ($errCode !== CURLM_OK) {
throw new Exception("curl_multi_exec执行失败: " . curl_multi_strerror($errCode));
}
curl_multi_select($mh); // 阻塞直至有数据可读,默认阻塞最多一秒,阻塞期间不占用CPU资源
} while ($running > 0);
/* 读取请求结果 */
while ($info = curl_multi_info_read($mh)) {
$ch = $info['handle'];
if ($info['result'] !== 0) {
$httpCode = $info['result']; // $info['result']的值等同于curl_errno($ch)
$httpBody = curl_error($ch);
} else {
$httpCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$httpBody = curl_multi_getcontent($ch);
}
$index = array_search($ch, $chHandles, true);
$requests[$index]['response'] = [
'code' => $httpCode, // HTTP状态码或cURL错误码
'body' => $httpBody, // HTTP body或cURL错误信息
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $requests;
}
function main()
{
/* 要请求的接口 */
$requests = [
[
'url' => 'https://www.baidu.com', // 请求URL
'method' => 'GET', // 请求方法,支持GET和POST
'header' => [], // 请求头
'param' => ['i' => 0], // 请求参数
],
[
'url' => 'http://www.ssssdhaidhoasdhaihd.com',
'method' => 'GET',
'header' => [],
'param' => ['i' => 1],
],
];
$results = concurrency_request($requests);
echo "请求结果: " . json_encode($results) . "\n";
}
main();
注意的地方
不要尝试从$chHandles
里的原始句柄处获取错误代码和错误信息,例如:
foreach ($chHandles as $ch) {
$errCode = curl_errno($ch); // 获取到的值永远为0
$errMsg = curl_error($ch); // 获取到的值永远为空字符串
}
这是因为curl_multi
在遇到请求错误时,不会把错误信息写入到原始句柄处,正确的做法是使用curl_multi_info_read
函数来获取句柄:
while ($info = curl_multi_info_read($mh)) {
$ch = $info['handle'];
$errCode = curl_errno($ch);
$errMsg = curl_error($ch);
if ($errCode !== 0) {
exit("出错啦");
}
}