此篇介绍一下php的curl_multi系列函数,用于批量发送http请求。

curl_multi原理

注意:CURL在PHP中的多线程处理其实并不是真正的多线程,而是用单线程批处理模拟的多线程效果。

curl_multi使用步骤

使用curl_multi的步骤总结如下:

  1. 调用curl_multi_init
  2. 循环调用curl_multi_add_handle,这里需要注意的是,curl_multi_add_handle的第二个参数是由curl_init而来的子handle
  3. 持续调用curl_multi_exec
  4. 根据需要循环调用curl_multi_getcontent获取结果
  5. 调用curl_multi_remove_handle,并为每个子handle调用curl_close
  6. 调用curl_multi_close

各函数作用解释

  1. curl_multi_init()
    初始化一个curl批处理句柄资源。

  2. curl_multi_add_handle()
    向curl批处理会话中添加单独的curl句柄资源。
    curl_multi_add_handle()函数有两个参数,第一个参数表示一个curl批处理句柄资源,第二个参数表示一个单独的curl句柄资源。

  3. curl_multi_exec()
    解析一个curl批处理句柄,curl_multi_exec()函数有两个参数,第一个参数表示一个批处理句柄资源,第二个参数是一个引用值的参数,表示剩余需要处理的单个的curl句柄资源数量。

  4. curl_multi_remove_handle()
    移除curl批处理句柄资源中的某个句柄资源,curl_multi_remove_handle()函数有两个参数,第一个参数表示一个curl批处理句柄资源,第二个参数表示一个单独的curl句柄资源。

  5. curl_multi_close()
    关闭一个批处理句柄资源。

  6. curl_multi_getcontent()
    在设置了CURLOPT_RETURNTRANSFER的情况下,返回获取的输出的文本流。

  7. curl_multi_info_read()
    获取当前解析的curl的相关传输信息。

示例代码

<?php
$start_time = microtime(true);
echo "\n";

$k = 10;
while ($k > 0) {
	$urls[] = "http://127.0.0.1:8080";
	$k = $k - 1;
}

print_r(async_get_urls($urls));
echo "\n";

$end_time = microtime(true);
echo $end_time - $start_time;
echo "\n";

function async_get_urls($urls)
{
	if (!is_array($urls))
		return false;

    $result = [];
    $handle = [];
    $active = 0;
    $mh = curl_multi_init();  // 初始化一个curl批处理句柄资源
    
    foreach($urls as $i => $url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  // 返回而非输出
        curl_setopt($ch, CURLOPT_TIMEOUT, 1);  // 控制每一个请求的超时时间
        /*
        // POST的数据
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postParams);
        */

        curl_multi_add_handle($mh, $ch); // 向curl批处理会话中添加单独的curl句柄资源
        $handle[$i] = $ch;
    }

    // 执行
    /* curl_multi_exec在底层调用了libcurl的curl_multi_perform函数。
    在curl7.20.0以前,此函数会返回一个CURLM_CALL_MULTI_PERFORM值,代表它希望立刻再一次被调用。所以就有了检查此返回值,再一次调用curl_multi_exec函数的demo。
    在7.20.0之后,libcurl把这个工作自己在内部做了,所以就不用应用端再做了,直接调用curl_multi_exec,只检查$still_running参数就行了。但此种用法要注意用usleep或者select优化,避免造成cpu占用过高出现假死 */
    /*
    do {
        curl_multi_exec($mh, $running); // 第二个参数表示剩余需要处理的单个curl句柄资源数量
        usleep(250000); // 250000 = 0.25 sec
    } while ($running > 0);
    */
    
    do {
            $mrc = curl_multi_exec($mh, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);  // CURLM_CALL_MULTI_PERFORM (-1):这意味着你需要再次调用curl_multi_exec(),因为仍有数据可供处理
    
    while ($active && $mrc == CURLM_OK) {  // CURLM_OK(0):如文档中所说:“都好了”。这意味着可能有更多的数据,但还没有到。
            if (curl_multi_select($mh) != -1) {
                do {
                    $mrc = curl_multi_exec($mh, $active);
                } while ($mrc == CURLM_CALL_MULTI_PERFORM);
            }
        }

    // 读取结果
    foreach($handle as $i => $ch) {
    	$content['total_time'] = curl_getinfo($ch)['total_time'];
        $content['error'] = curl_error($ch);
        $content['data']  = json_decode(curl_multi_getcontent($ch), true);
        $result[$i] = (curl_errno($ch) == 0) ? $content : false;
    }

    // 移除handle
    foreach($handle as $ch) {
        curl_multi_remove_handle($mh, $ch);  // 移除curl批处理句柄资源中的某个句柄资源
    }

    curl_multi_close($mh);  // 关闭批处理句柄资源

    return $result;
}