关于php的libevent扩展的应用
php有个libevent扩展,在一年前我曾经拿它实现了一个thrift socket server,虽然我没有把它放在正式的场合来使用,但是我觉得这个扩展应该可以有更广泛的用途,比如:
- phpDaemon — 一个异步的服务器端开发框架.
- tail – 用php实现类似unix下的tail命令行
- ZeroMQ + libevent in PHP – 用php和ZeroMQ实现的一个事件驱动服务器端
我所想到的一个比较实用的使用场景是,在页面中利用libevent请求多个http接口来获得数据。若是在从前,一个可行的办法是利用curl_multi_exec来同时请求好几个接口,但是这个办法需要用一个do … while循环来完成请求,很是坑爹。那么看看采用libevent的例子:
代码实例 http.php
<?php
$base_fd = event_base_new();
$times = 0;
$limit = 10;
$index = array();
function httpGet($host, $base_fd) {
global $index;
$fd = stream_socket_client("$host:80", $errno, $errstr, 3, STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT);
$index[$fd] = 0;
$event_fd = event_new();
event_set($event_fd, $fd, EV_WRITE | EV_PERSIST, function($fd, $events, $arg) use($host) {
global $times, $limit, $index;
if(!$index[$fd]) {
$index[$fd] = 1;
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fd, $out);
} else {
echo fread($fd, 4096);
if(feof($fd)) {
fclose($fd);
$times++;
echo "done\n";
if($times == $limit - 1) {
event_base_loopexit($arg[1]);
}
}
}
}, array($event_fd, $base_fd));
event_base_set($event_fd, $base_fd);
event_add($event_fd);
}
for($i = 0; $i < $limit; $i++) {
echo "$i\n";
httpGet($argv[1], $base_fd);
//echo file_get_contents("http://$argv[1]");
}
event_base_loop($base_fd);
?>
为了省事,这个php脚本仅仅是重复抓取一个网页5次,并且回调的逻辑我没怎么做处理,仅仅是echo出来而已,可以通过下面命令行来运行这个例子:
php http.php "www.baidu.com"
代码中的http_get($argv[1])这行虽然是靠一个命令行顺序执行,但是不会阻塞后面的代码,直接就进行下一次请求了。而且我们看看回调方法部分是不是很像用javascript调用ajax写的回调方法?这都是php 5.3中闭包的功劳。
event_set($event_fd, $fd, EV_WRITE | EV_PERSIST, function($fd, $events, $arg) {
//回调方法,后续处理随意
echo fread($fd, 4096);
if(feof($fd)) {
fclose($fd);
event_base_loopexit($arg[1]);
echo "done";
}
}, array($event_fd, $base_fd));
想到更多
在mysqlnd,memcached…这些php扩展中,都已经有delay回调的实现,如果能好好利用,对性能提升岂不是有莫大的帮助?或者在libevent扩展的基础上,实现一个事件驱动的开发框架,也是可行的。
Update 2011.11.10
在这个代码的基础上实现了一个异步http请求的客户端
Update 2011.10.28
event_base_loop是会阻塞后续代码执行的,所以我调整了示例代码,使用同一个event_base,并且用stream_socket_client来进行异步连接,另外在/etc/hosts指定域名的ip会对执行速度有帮助。
作者: Volcano 发表于October 25, 2011 at 10:58 pm
Jacky 于 2011-10-26 @ 05:55:54 留言 :
好文章。正好我最近也在看这方面的东西,所以也在这边讨论一下。
多个http_get是否应该共用一个event_base?
函数内定义的event是否会被GC? (我自己做过一个实验,貌似是被GC了)。
phpDaemon里面没有涉及到worker和master之间的通信问题。我的考虑是这样的——如果语言本身不支持线程级别的操作,则master/worker模型在网络服务器的应用场景下意义不大。比如:一个network process接收数据,再把这个数据通过unix domain socket发给worker处理,最后再发回。这种方式不如直接运行于单进程情况下启动多个进程好了——本身unix domain socket就是一种开销。
不知道你怎么看?
Volcano 于 2011-10-26 @ 08:59:18 留言 :
>> 多个http_get是否应该共用一个event_base?
共用event_base是否能区分不同的http请求,在请求完成之后,我使用了event_base_loopexit来退出循环,也会对这个产生影响。
>> 函数内定义的event是否会被GC?
你自己回答了,呵呵
另外关于phpDaemon这种我没有做深入研究,现在我主要关注如何利用libevent来减少常规php网页中的阻塞问题。
神仙 于 2011-10-26 @ 16:00:55 留言 :
event_base_loop 执行到这个函数会阻塞住。所以你这个代码实际还是串行的。
其实就是应该共用同一个 event_base 。区分请求可以通过额外参数,回调的时候会带上。
Volcano 于 2011-10-27 @ 11:00:33 留言 :
测试了一下,不仅是event_base_loop会阻塞,fsockopen也会阻塞,换成stream_socket_client加上STREAM_CLIENT_ASYNC_CONNECT才可以通过,但是这个我没有测试成功。另外event_base的确应该共用,这方面的示例代码少了点。
Jacky 于 2011-10-27 @ 16:19:10 留言 :
正想说event_base_loop会阻塞,然后就看到回复了,呵呵。
其实可以考虑把event作为类内属性,这样可以直接把array($this, ‘method’)作为callback挂上。
Volcano 于 2011-10-28 @ 15:52:23 留言 :
@Jacky 把event做为类内属性完全没问题,从phpDaemon的代码里可以看到很多类似的写法。这个例子里主要是为了方便阅读理解,直板的写了下来。
另外我已经更新了代码,共用同一个event_base,执行时间有减少,但是有时仍有短暂的阻塞。
Jacky 于 2011-10-30 @ 10:30:36 留言 :
@Volcano – 是不是有个问题:为什么在EV_WRITE回调函数里面放read?
Anonymous 于 2011-11-01 @ 20:16:56 留言 :
赞,
我们也实现了php的epoll、mmap等扩展,在这基础上实现了一套php web server,在线上运营效果还不错,期待大家一起交流,请加我qq 272637398详聊,谢谢。
Volcano 于 2011-11-02 @ 09:28:54 留言 :
有在线上运营的么,可否放出连接让大家参观一下?