プログラミング

async_socketクラス

2010年4月27日

少し思うところあって、非同期のソケットクラスをPHPで書いてみた。

たぶん、こういった用途のライブラリは色々とあるのだろうけど、スクラッチから書くのが好きなので…。

ソースコード
<?php

echo microtime()."\n";

$kandk=new async_socket;
$kandk->register_callback('cb',array($kandk));
$kandk->request('kandk.cafe.coocan.jp','/');

$kandk2=new async_socket;
$kandk2->register_callback('cb',array($kandk2));
$kandk2->request('kandk.cafe.coocan.jp','/nucleus/');

$google=new async_socket;
$google->register_callback('cb',array($google));
$google->request('www.google.com','/');


$sockets=array($kandk,$kandk2,$google);

$continue=true;
while($continue){
    $continue=false;
    foreach($sockets as $obj) {
        if ($obj->check()) $continue=true;
    }
    if ($continue) usleep(100000);
}
echo "all done";

function cb($obj){
    $len=strlen($obj->reply);
    echo microtime()." Got {$len} bytes from {$obj->hostname}\n";
}

class async_socket {
    private $hostname,$port,$uri;
    private $res=false,$busy=false,$errno=false,$errstr=false;
    private $reply='';
    private $callback=false,$args=array();
    public function __get($name){
        // Return private property as "read only" value.
        if (isset($this->$name)) return $this->$name;
        return null;
    }
    public function register_callback($callback,$args=array()){
        $this->callback=$callback;
        $this->args=$args;
    }
    public function request($hostname,$uri='/',$port=80,$timeout=20){
        if ($this->res) return false;
        // Connect
        $this->hostname=$hostname;
        $this->port=$port;
        $this->uri=$uri;
        $this->res=fsockopen($hostname,$port,$errno,$errstr,$timeout);
        if (!$this->res) return false;
        // Request
        stream_set_blocking($this->res,0);
        $request="GET $uri HTTP/1.1\r\nHost: $hostname\r\nConnection: Close\r\n\r\n";
        if (!fwrite($this->res,$request)) return false;
        $this->busy=true;
        return true;
    }
    public function check(){
        // Return true if still busy.
        // If connection closed, call callback-function (if registered).
        if (!$this->busy) return false;
        if (feof($this->res)) {
            $this->res=false;
            $this->busy=false;
            if ($this->callback) call_user_func_array($this->callback,$this->args);
            return false;
        }
        while (strlen($got=fread($this->res,1024))) $this->reply.=$got;
        return true;
    }
}

実行結果
0.49500300 1272399245
0.03582900 1272399246 Got 9048 bytes from www.google.com
0.25463900 1272399246 Got 3585 bytes from kandk.cafe.coocan.jp
0.00156700 1272399248 Got 58650 bytes from kandk.cafe.coocan.jp
all done

async_socketクラスは、GETメソッドで呼び出し、返事を受け取るとコールバック関数を実行する単純な仕様。POSTメソッドを利用したり拡張ヘッダ情報の送信
、あるいはKeep-Aliveな接続などを行いたい時は、スクリプトを修正すべし。

ソースコードの半ばあたり、"if ($continue) usleep(100000);"の行をコメントアウトすると、スクリプトの実行中はCPUの占有率が100%になる。このあたりの設定は、スピードとリソース消費の兼ね合いで、調整が必要なところ。今のような用途だと、100000マイクロ秒(0.1秒)のスリープで、リソース消費も最低限だし実行速度も問題なさそうだ。

コメント

コメントはありません

コメント送信