curl_multi_* 함수를 이용.

curl 버전 7.20.0 이전과 이후의 curl_multi_exec() 반환값 핸들링이 다르므로 그 차이만 주의하면 됨.

여러 url을 순차적으로 호출하는 것보다 확실히 빠름.

 

함수와 사용 예제.

<?PHP
 
 
/*
URL 배열을 받아 비동기 방식으로 호출하고 그 결과값을 반환
 
 
[INPUT]
array(
    'URL1', 'URL2', 'URL3', ...
)
 
 
[RETURN]
array(
    'URL1' => array(
        'err' => '',     // 에러 메시지. 없으면 빈 문자열
        'content' => '', // 호출 결과 문자열
        't' => 0         // 호출 소요시간
    ),
    'URL2' => array(
        'err' => '',
        'content' => '',
        't' => 0
    ),
    'URL3' => array(
        'err' => '',
        'content' => '',
        't' => 0
    ),
    ...
)
*/
function curl_multi($urlList)
{
    $chList = array();
    $resultList = array();
    $handleUrlMap = array();
    foreach ($urlList as $url) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 
        $chList[$url] = $ch;
 
        $handleUrlMap[$ch] = $url;
 
        $resultList[$url] = array(
            'err' => '',
            'content' => '',
            't' => 0
        );
    }
 
    $cmh = curl_multi_init();
    foreach ($chList as $ch) {
        curl_multi_add_handle($cmh, $ch);
    }
 
 
    $t = microtime(true);
    $curlVersion = curl_version();
 
    // 7.20.0 이후(>=) 버전
    if (version_compare($curlVersion['version'], '7.20.0', '>=') === true) {
        do {
            $status = curl_multi_exec($cmh, $active);
            while (false !== ($info = curl_multi_info_read($cmh))) {
                $url = $handleUrlMap[$info['handle']];
                $resultList[$url]['t'] = microtime(true)-$t;
 
                // error
                if ($info['result'] != CURLE_OK) {
                    $resultList[$url]['err'] = curl_error($info['handle']);
                }
            }
            if ($active) {
                curl_multi_select($cmh);
            }
 
        } while ($active && $status == CURLM_OK);
    }
    // 7.20.0 이전 옛날 버전
    else {
        do {
            $status = curl_multi_exec($cmh, $active);
            if ($status === CURLM_CALL_MULTI_PERFORM) continue;
 
            curl_multi_select($cmh);
 
            while (false !== ($info = curl_multi_info_read($cmh))) {
                $url = $handleUrlMap[$info['handle']];
                $resultList[$url]['t'] = microtime(true)-$t;
 
                // error
                if ($info['result'] != CURLE_OK) {
                    $resultList[$url]['err'] = curl_error($info['handle']);
                }
            }
        } while ($active);
    }
 
    foreach ($chList as $url=>$ch) {
        if (strlen($resultList[$url]['err']) > 0) continue;
 
        $resultList[$url]['content'] = curl_multi_getcontent($ch);
 
        curl_multi_remove_handle($cmh, $ch);
        curl_close($ch);
    }
 
    curl_multi_close($cmh);
 
    return $resultList;
}
 
 
 
 
 
 
$urlList = array();
// 1페이지부터 10페이지까지 비동기로 한 번에 가져온다고 가정
for ($i=1; $i<=10; $i++) {
    $urlList[] = 'https://mydomain.com/list.php?page='.$i;
}
 
 
$resultList = curl_multi($urlList);

 

 

curl 버전에 따른 차이점에 대해 자세히 설명하자면,

7.20.0 이전 버전의 curl_multi_exec() (libcurl의 curl_multi_perform()에 해당) 함수는 CURLM_CALL_MULTI_PERFORM (-1) 을 반환하기도 하는데,

이건 실제로는 에러가 아니라 select()를 호출하지 말고 curl_multi_exec()를 재호출 하라는 뜻이다.

이로 인해 7.20.0 이전/이후의 로직이 미묘하게 달라지게 된다.

참고: https://curl.haxx.se/libcurl/c/libcurl-errors.html#CURLMCALLMULTIPERFORM

 

 

 

Posted by bloodguy

댓글을 달아 주세요

  1. ㅇㅇ 2021.05.30 10:50  댓글주소  수정/삭제  댓글쓰기

    힘들게 curl 쓰지말고 php http 클라이언트인 guzzle 을 쓰는게 낫지 않나요

    PSR-7 을 따르는 많은 http 클라이언트 중에 guzzle이 낫네요