LL Planetにて
先日行ってきたLL Planetにて非同期処理について話しているシーンがあって、HTTPのリクエストを複数送って、後で待つ処理はWebAPIを扱う上で便利だなと思った。PHPならfile_get_contentsを非同期で扱えるだろうと思ったら、まったくできない。そもそも、PHPで非同期処理がまったく(?)扱えない。
ちょっと悔しいので、PHPだったもう少し頑張れることを証明しよう!という気になったわけです。
fscoketopenとsocket_set_blocking
PHPはhttpでGETリクエストを送るだけなら、fopenとかfile_get_contentsをすれば十分。複数のWebAPIをサーバー側で送りたい場合、リクエストが帰ってくるまで待たないといけないので、非常に非効率な実装をすることになる。
fscoketopenとsocket_set_blockingを使うことで、ブロックせずにリクエストが送れるそうなので、ちょっとソースを書いてみた。
async.php
function http_get($url) { $parseUrl = parse_url($url); if(!isset($parseUrl['port'])){ $parseUrl['port'] = 80; } $path = $parseUrl['path']; if(isset($parseUrl['query'])) $path .= '?' . $parseUrl['query']; if(isset($parseUrl['fragment'])) $path .= '#' . $parseUrl['fragment']; $fp = fsockopen ($parseUrl['host'], $parseUrl['port'], $errno, $errstr, 5); if (!$fp) { echo "Error: $errstr ($errno)<br>\n"; } else { fputs ($fp, "GET ". $path ." HTTP/1.0\r\n\r\n"); socket_set_blocking($fp, false); } return $fp; }
fsockopenでソケットを開いて、HTTPヘッダーを書き込む(ヘッダーの作り方を詳しく知らないので最低限動くようにしかなっていない)
その後、socket_set_blockingを使ってブロックしないようにする
。
wait.php
<?php sleep($_GET['time']); echo "wait complete.".$_GET['time'];
実験用の指定時間だけ待つスクリプト
async_demo.php
<?php require_once dirname(__FILE__).'/async.php'; function microtime_float() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec); } $time_start = microtime_float(); // request $handles = array( async\http_get( "http://localhost:8888/async_test/wait.php?time=1&dummy=100#hogehoge" ), async\http_get( "http://localhost:8888/async_test/wait.php?time=2&dummy=100#hogehoge" ) ); // wait response async\wait_response( $handles ); // get response foreach($handles as $handle){ echo async\get_response($handle) . "<br>"; } $time_end = microtime_float(); echo "time=" . ( $time_end - $time_start );
複数リクエストを送って、全体の実行時間を計測するスクリプトで実験。一つ目のリクエストが返ってくるのが1秒、二つ目のリクエストが返ってくるのが2秒かかる。同期処理なら3秒かかるが非同期処理なら2秒で終わる。
今回作った関数の役割は、
http_getでリクエストを投げて、wait_responseで処理待ち、get_responseでレスポンスを取得。
結果↓ちゃんと2秒で返ってきました。
HTTP/1.1 200 OK Date: Sun, 04 Sep 2011 07:29:01 GMT Server: Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8r DAV/2 PHP/5.3.6 X-Powered-By: PHP/5.3.6 Content-Length: 15 Connection: close Content-Type: text/html wait complete.1 HTTP/1.1 200 OK Date: Sun, 04 Sep 2011 07:29:01 GMT Server: Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8r DAV/2 PHP/5.3.6 X-Powered-By: PHP/5.3.6 Content-Length: 15 Connection: close Content-Type: text/html wait complete.2 time=2.00401997566
結果
無事、リクエストを投げる処理を非同期で行えた。
そもそも、スレッドはないが重い処理をPHPのサーバーで行うことは今のところ想定していないので、これだけあれば十分だなと感じた。
nilfs/async_php · GitHub に今回のソースを配置しておきました。