指定したIPで並列HTTPリクエストする方法

IPアドレスが複数割り当てられているサーバから指定したIPでHTTPリクエストを送りたかったのでそれのメモ。
PHPから普通にHTTPリクエストを送信する方法はいろいろあります。

パッと思いついたのを書いていくとこんな感じ。

  1. cURL
  2. file_get_contents
  3. fsockopen
  4. ソケット関数
  5. PEAR::HTTP_Request

PEAR::HTTP_Requestについてはライブラリです、実際の中身は確かfsockopenで実装されていたはず。
で、上記の方法のうち4以外の方法だとクライアント側のIPアドレスを指定してリクエストを送信することはできない。
* 2012/01/28 - 他の方法でも実装出来ることを教えてもらったので修正

4の場合はsocket_bindを利用すればクライアント側のIPを指定してリクエストを送信することができる。

最初にテストで書いてみたのが下記のコード

<?php
$ipaddress = 'xxx.xxx.xxx.xxx';

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($sock, $ipaddress);
socket_connect($sock, 'example.com', 80);
socket_write($sock, "GET / HTTP/1.0\r\n\r\n");
$buffer = socket_read($sock, 8192);
echo $buffer;

結果、ちゃんとIPアドレスを指定してリクエストが送れた。

で、更に並列にリクエストを何個も送信したかったので、プロセスをforkさせてリクエストを送信するようなプログラムを書いてたんだけど、
なぜかうまくいかない…

ソケットはブロッキングされないようにちゃんとsocket_set_nonblockしてる。

エラーを見てみると、Operation now in progressってのが大量に発生してる。
Operation now in progressを調べてみるが問題解決には繋がらず。
で、ふとsocket_connect関数のマニュアルを読んでる時に気づいた。

返り値
成功した場合に TRUE を、失敗した場合に FALSE を返します。 エラーコードは、 socket_last_error() により取得できます。 このコードを socket_strerror() に渡すことにより、 エラー内容を表すテキストを得ることができます。


注意:
ソケットが非ブロッキングモードの場合、この関数は FALSE を返し、エラー Operation now in progress を発生させます。

http://jp.php.net/socket_connect

なんと!注意のところを見てなかった…

だから、socket_connect関数が使われてるコードにはたまに先頭に@がついてるのかーと思った。
で、更に色々調べてるうちにプロセスをforkしなくても並列にリクエストを送信する方法がわかったのでそれも下記のコードに示す。

<?php
$requests = array(
    'xxx.xxx.xxx.xxx' => 'example.com',
    'yyy.yyy.yyy.yyy' => 'example.com',
    'zzz.zzz.zzz.zzz' => 'example.com',
);

$socks = array();

// ソケットを予め作っておく
foreach ($requests as $ipaddress => $host) {
    $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    socket_bind($sock, $ipaddress);
    socket_set_nonblock($sock);
    $result = @socket_connect($sock, $host, 80);
    if (true == $result || SOCKET_EINPROGRESS !== socket_last_error()) {
        exit('error!');
    }
    $socks[] = $sock;
}

$responses = array();

// リクエストの送信・受信
while (count($socks)) {
    $writes = $reads = $socks;
    $excepts = null;
    
    $num = socket_select($reads, $writes, $excepts, 120);
    if ($num) {
        foreach ($writes as $write) {
            socket_write($write, "GET / HTTP/1.0\r\n\r\n");
            socket_shutdown($write, 1);
        }

        foreach ($reads as $read) {
            $id = array_search($read, $socks);
            $responses[$id] .= $buffer = socket_read($read, 8192);
            if (0 == strlen($buffer)) {
                socket_close($read);
                unset($socks[$id]);
            }
        }
    }
}

print_r($responses);

こんな感じでクライアントのIPアドレスを指定して、並列にHTTPリクエストが送信できる。