今日は、PHP の強力な武器である cURL 関数ライブラリの使用法とそのマルチスレッド最適化メソッドを紹介します。 POSTリクエストの送信をシミュレートします。
cURL関数と言えば、ありきたりな言葉ですが、マニュアルには重要な部分が不明瞭なことが多く、最初に情報をめくったときは非常に苦痛でした。このブログ投稿を私自身のメモと組み合わせて要約することで、cURL を初めて使用する開発者に役立つことを願っています。
cURLを使用する基本手順
まずcURLを紹介しましょう:
cURL は、HTTP ヘッダー情報に従ってブラウザーのデータ送信をシミュレートします。FTP、FTPS、HTTP、HTTPS、DICT、FILE およびその他のプロトコルをサポートします。HTTPS 認証、HTTP POST メソッド、HTTP PUT メソッド、FTP アップロード、HTTP アップロード、プロキシを備えています。サーバー、Cookie、ユーザー名/パスワード認証、その他の機能。 cURL は、Web サイトのクローリング、Web ページの取得、POST データなどの機能を実現する強力なツールであると言えます。
cURL 関数の使用は主に 4 つの部分に分かれています:
1.cURLを初期化します。
2. cRUL の核となる cURL 変数を設定します。
3. cURL を実行して結果を取得します。
4. 接続を閉じてリソースをリサイクルします。
リーリーさらに、curl_getinfo($ch) 関数を使用して、curl の実行情報を取得することもでき、結果は配列になります
$info 配列の内容には次のものが含まれます:
cURLの共通設定
以下は、2番目のステップでcurlを使用するときによく使用される変数設定について詳しく紹介します。curl関数を使用する場合、さまざまなニーズに応じて設定できます。
基本情報を設定します:
curl_setopt($ch, CURLOPT_URL, $string);//curlのディレクトリアドレスを設定します
curl_setopt($ch, CURLOPT_PORT, $port);//接続ポートを設定します。通常はデフォルトの 80 には設定されません
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//結果ストリームを返しても、後続の処理のために出力されません。通常、この項目は、キャプチャされた情報を直接出力するのではなく、後で処理するように設定されます。
POSTデータ情報の設定:
curl_setopt($ch, CURLOPT_POST, 1);//データ送信方法をPOSTに設定します
curl_setopt($ch, CURLOPT_POSTFIELDS, $string);//送信するデータを設定します
認証情報を設定します:
curl_setopt($ch, CURLOPT_COOKIE, $string);//curl の実行時に保持される Cookie 情報を設定します
curl_setopt($ch, CURLOPT_USERAGENT, $string);//curl シミュレーション用のブラウザ情報を設定します
curl_setopt($ch, CURLOPT_REFERER, $string);//アンチリーチングを解読するためにヘッダーにリファラーを設定します
curl_setopt($ch, CURLOPT_USERPWD, $string);//接続に必要なユーザー名とパスワードを次の形式で渡します: "[ユーザー名]:[パスワード]"
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);//サーバーのリダイレクトを許可するように設定します
拡張情報を設定します:
curl_setopt($ch, CURLOPT_NOBODY, 1);//ページタイトルなどの情報をクロールする際に、HTML本文の出力を許可しないように設定すると、処理が大幅に高速化されます
curl_setopt($ch, CURLOPT_TIMEOUT, $int);// 実行に許可される最大秒数 (タイムアウト時間) を設定します。値を小さい値に設定すると、CURL は実行時間の長いページを破棄します
。curl_setopt($ch, CURLOPT_HEADER, 1);//ターゲットの読み取り時に生成されるヘッダー ヘッダー ファイルを出力ストリームに含めることを許可するように設定します
cURLバッチ処理機能の基本的な使い方
もちろん、cURL の機能はそれだけではありません。マニュアルにはさらに多くの変数設定が記載されています。そして cURL の最も強力な部分はバッチ処理機能です。
cURL のバッチ処理も理解しやすいようです。一般的な手順は次のとおりです。
1.$mh =curl_multi_init();//バッチハンドルを初期化します。2.curl_multi_add_handle($mh,$ch); //設定された $ch ハンドルをバッチ ハンドルに追加します。
3.curl_multi_exec($mh,$running);//$mh ハンドルを実行し、$mh ハンドルの実行ステータスを $running 変数に書き込みます
4. $running が true の場合、curl_multi_close() 関数をループします
5.循环结束后遍历$mh句柄,用curl_multi_getcontent()获取第一个句柄的返回值
6.用curl_multi_remove_handle()将$mh中的句柄移除
7.用curl_multi_close()关闭$mh批处理句柄。
代码如下:
<?<span>php </span><span>$chArr</span>=<span>[]; </span><span>for</span>(<span>$i</span>=0;<span>$i</span><50;<span>$i</span>++<span>){ </span><span>$chArr</span>[<span>$i</span>]=curl_init("http://www.baidu.com"<span>); curl_setopt(</span><span>$chArr</span>[<span>$i</span>],CURLOPT_RETURNTRANSFER,1<span>); } </span><span>$mh</span> = curl_multi_init(); <span>//</span><span>1</span> <span>foreach</span>(<span>$chArr</span> <span>as</span> <span>$k</span> => <span>$ch</span><span>){ curl_multi_add_handle(</span><span>$mh</span>,<span>$ch</span>); <span>//</span><span>2</span> <span> } </span><span>$running</span> = <span>null</span><span>; </span><span>do</span><span>{ curl_multi_exec(</span><span>$mh</span>,<span>$running</span>); <span>//</span><span>3</span> <span> }</span><span>while</span>(<span>$running</span> > 0); <span>//</span><span>4</span> <span>foreach</span>(<span>$chArr</span> <span>as</span> <span>$k</span> => <span>$ch</span><span>){ </span><span>$result</span>[<span>$k</span>]= curl_multi_getcontent(<span>$ch</span>); <span>//</span><span>5</span> <span> curl_multi_remove_handle(</span><span>$mh</span>,<span>$ch</span>);<span>//</span><span>6</span> <span> } curl_multi_close(</span><span>$mh</span>); <span>//</span><span>7</span> ?>
cURL批处理时内存占用过多的问题
但是,执行大批量的句柄时我们会发现一个很严重的问题,那就是执行时系统CPU占用率几乎100%,几乎是死机状态了。纠其原因,那是因为在$running>0,执行 curl_multi_exec($mh,$running)而整个批处理句柄没有全部执行完毕时,系统会不停地执行curl_multi_exec()函数。我们用实验来证明:
我们在循环中curl_multi_exec($mh,$running)句前加入一个echo "a";的语句。我们的目的是执行50次对百度的访问,然后来看一下结果。
从图中滚动条的大小(滚动条已经最小状态了)可以大概看出输出a的个数,500个也不止,所以我们便可以找到占用CPU的罪魁祸首了。
cURL批处理时的内存优化方案
进行改动的方式是应用curl函数库中的curl_multi_select()函数,其函数原型如下:
<p>int curl_multi_select ( resource $mh [, float $timeout = 1.0 ] )</p> <p>阻塞直到cURL批处理连接中有活动连接。成功时返回描述符集合中描述符的数量。失败时,select失败时返回-1,否则返回超时(从底层的select系统调用)。</p>
我用们curl_multi_select()函数来达到没有需要读取的程序就阻塞住的目的。
我们对批处理的第3、4步进行优化,利用其多线程,模拟并发程序。
很多朋友会对手册中提供的代码心存疑惑(我一开始也是),下面的代码及解释。
<span>$running</span> = <span>null</span><span>; </span><span>do</span><span> { </span><span>$mrc</span> = curl_multi_exec(<span>$mh</span>, <span>$running</span><span>); } </span><span>while</span> (<span>$mrc</span> ==<span> CURLM_CALL_MULTI_PERFORM); </span><span>//</span><span>本次循环第一次处理$mh批处理中的$ch句柄,并将$mh批处理的执行状态写入$running,当状态值等于CURLM_CALL_MULTI_PERFORM时,表明数据还在写入或读取中,执行循环,当第一次$ch句柄的数据写入或读取成功后,状态值变为CURLM_OK,跳出本次循环,进入下面的大循环之中。 //$running为true,即$mh批处理之中还有$ch句柄正待处理,$mrc==CURLM_OK,即上一次$ch句柄的读取或写入已经执行完毕。</span> <span>while</span> (<span>$running</span> && <span>$mrc</span> ==<span> CURLM_OK) { </span><span>if</span> (curl_multi_select(<span>$mh</span>) != -1) {<span>//</span><span>$mh批处理中还有可执行的$ch句柄,curl_multi_select($mh) != -1程序退出阻塞状态。</span> <span>do</span> { <span>//</span><span>继续执行需要处理的$ch句柄。</span> <span>$mrc</span> = curl_multi_exec(<span>$mh</span>, <span>$running</span><span>); } </span><span>while</span> (<span>$mrc</span> ==<span> CURLM_CALL_MULTI_PERFORM); } }</span>
这样执行的好处是$mh批处理中的$ch句柄会在读取或写入数据结束后($mrc==CURLM_OK),进入curl_multi_select($mh)的阻塞阶段,而不会在整个$mh批处理执行时不停地执行curl_multi_exec,白白浪费CPU资源。
cURL批处理的内存优化结果
完整代码如下:
<?<span>php </span><span>$chArr</span>=<span>[]; </span><span>for</span>(<span>$i</span>=0;<span>$i</span><50;<span>$i</span>++<span>){ </span><span>$chArr</span>[<span>$i</span>]=curl_init("http://www.baidu.com"<span>); curl_setopt(</span><span>$chArr</span>[<span>$i</span>],CURLOPT_RETURNTRANSFER,1<span>); } </span><span>$mh</span> =<span> curl_multi_init(); </span><span>foreach</span>(<span>$chArr</span> <span>as</span> <span>$k</span> => <span>$ch</span><span>) curl_multi_add_handle(</span><span>$mh</span>,<span>$ch</span><span>); <br /> </span><span>$running</span> = <span>null</span><span>; </span><span>do</span><span> { <br /></span><span> $mrc</span> = curl_multi_exec(<span>$mh</span>, <span>$running</span><span>); } </span><span>while</span> (<span>$mrc</span> ==<span> CURLM_CALL_MULTI_PERFORM); </span><span>while</span> (<span>$running</span> && <span>$mrc</span> ==<span> CURLM_OK) { </span><span>if</span> (curl_multi_select(<span>$mh</span>) != -1<span>) { </span><span>do</span><span> { </span><span>$mrc</span> = curl_multi_exec(<span>$mh</span>, <span>$running</span><span>); } </span><span>while</span> (<span>$mrc</span> ==<span> CURLM_CALL_MULTI_PERFORM); } } </span><span>foreach</span>(<span>$chArr</span> <span>as</span> <span>$k</span> => <span>$ch</span><span>){ </span><span>$result</span>[<span>$k</span>]= curl_multi_getcontent(<span>$ch</span><span>); curl_multi_remove_handle(</span><span>$mh</span>,<span>$ch</span><span>); } curl_multi_close(</span><span>$mh</span><span>); </span>?>
我们再次在 $mrc = curl_multi_exec($mh, $running)句子前加入echo "a";结果如下图:
虽然也不止50次,但是比之未优化前,CPU使用率已经大为改观。
虽然curl函数非常强大,但是我们还是有使用其他函数来发送POST请求的机会,另外也能从更底层了解curl函数,所以本辑也用大很大篇幅在其他函数上。
OK,本辑结束,写这辑博文的同时,我也学习到了很多。如果您觉得本博文对您有帮助,请您点推荐或关注我,我们继续分享我的笔记总结。如果有什么问题,您可以在下方留言讨论,谢谢阅读。