标签 php 下的文章

PHP 之 CURL学习(二)

  • curl学习(一)中所做的工作都是针对一个url的单次请求,当要处理1个URL队列时, 为了提高性能, 可以采用CURL提供的curl_multi_*族函数实现简单的并发.
$urls = array(
'http://www.cnblogs.com',
'http://www.google.com.hk',
'http://www.baidu.com',
'http://www.weibo.com',
'http://www.comsenz.com',
'http://www.csdn.net',
);
$opt_arr = array(
CURLOPT_HEADER => FALSE,
CURLOPT_TIMEOUT => 3,
CURLOPT_RETURNTRANSFER => TRUE,
);
$multi = curl_multi_init();//句柄初始化
//参数设置
foreach($urls as $key => $url) {
$ch_arr[$key] = curl_init();//初始化一个curl资源
$opt_arr[CURLOPT_URL] = $url;//设置url
curl_setopt_array($ch_arr[$key], $opt_arr);//参数设置
curl_multi_add_handle($multi, $ch_arr[$key]);//加入句柄队列
}

$isrunning = NULL;//预定义一个状态变量
do {//执行批处理句柄
$mrc = curl_multi_exec($multi, $isrunning);//$isrunning 一个用来判断操作是否仍在执行的标识的引用。
} while($mrc == CURLM_CALL_MULTI_PERFORM); //常量 CURLM_CALL_MULTI_PERFORM 代表还有一些刻不容缓的工作要做

while($isrunning && $mrc == CURLM_OK) {
if(curl_multi_select($multi) != -1) {//curl_multi_select阻塞直到cURL批处理连接中有活动连接,失败时返回-1
do {
$mrc = curl_multi_exec($multi, $isrunning);
} while($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
//所有请求接收完之后进行数据的解析等后续处理
foreach($ch_arr as $key => $ch) {
//获取内容进行后续处理
$contents = curl_multi_getcontent($ch);
//do something to deal data
curl_multi_remove_handle($multi, $ch_arr[$key]);//关闭句柄
curl_close($ch);
}
curl_multi_close($multi);

注意:CURL在PHP中的多线程处理其实并不是真正的多线程,而是用单线程批处理模拟的多线程效果。

上述代码显然有缺陷:数据处理都是在所有url请求接收完成以后才进行的,如果某些url处理比较慢显然就耽误了整个队列的处理时间,造成了CUP的闲置和浪费,这显然不是我们想要的结果。但是我们可以这样处理:每当一个url请求完成就开始处理,同时等待其它url请求返回。代码如下:

$urls = array(
'http://www.cnblogs.com',
'http://www.google.com.hk',
'http://www.baidu.com',
'http://www.weibo.com',
'http://www.comsenz.com',
'http://www.csdn.net',
'http://www.php.net',
);
$opt_arr = array(
CURLOPT_HEADER => FALSE,
CURLOPT_TIMEOUT => 5,
CURLOPT_RETURNTRANSFER => TRUE,
);
//句柄初始化
$multi = curl_multi_init();
//参数设置
foreach($urls as $key => $url) {
$ch_arr[$key] = curl_init();//初始化一个curl资源
$opt_arr[CURLOPT_URL] = $url;//设置url
curl_setopt_array($ch_arr[$key], $opt_arr);//参数设置
curl_multi_add_handle($multi, $ch_arr[$key]);//加入句柄队列
}
//预定义一个状态变量
$isrunning = NULL;
//执行批处理句柄
do {
while(($mrc = curl_multi_exec($multi, $isrunning)) == CURLM_CALL_MULTI_PERFORM);//$isrunning 一个用来判断操作是否仍在执行的标识的引用。
if($mrc != CURLM_OK)
break;
while($done = curl_multi_info_read($multi)) {//成功时返回相关信息的数组,失败时返回FALSE
$key = array_search($done['handle'], $ch_arr);
if(curl_getinfo($done['handle'], CURLINFO_HTTP_CODE) == '200') {
$content = curl_multi_getcontent($done['handle']);
//deal $content
echo ++$j,": $urls[$key] : ",strlen($content),"\n";
curl_multi_remove_handle($multi, $done['handle']);
curl_close($done['handle']);
} else {
echo ++$j,": ",$urls[$key],": ",curl_error($done['handle']),"\n";
}
}
} while($isrunning);
curl_multi_close($multi);

上述代码仍然是有缺陷的,不知道聪明的读者您发现没有?当URL队列很大时(比如1000),这就是一个大并发显然不合理的,参考这里

function rolling_curl($urls, $callback, $custom_options = null) {

$rolling_window = 5;
$rolling_window = (sizeof($urls) < $rolling_window) ? sizeof($urls) : $rolling_window;

$master = curl_multi_init();
$curl_arr = array();

//设置curl参数
$std_options = array(CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5);
$options = ($custom_options) ? ($std_options + $custom_options) : $std_options;

//初始化curl资源队列
$arr_chs = array();
for ($i = 0; $i < $rolling_window; $i++) {
$arr_chs[$urls[$i]] = curl_init();
$options[CURLOPT_URL] = $urls[$i];
curl_setopt_array($arr_chs[$urls[$i]],$options);
curl_multi_add_handle($master, $arr_chs[$urls[$i]]);
}

do {
while(($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
if($execrun != CURLM_OK) {
break;
}
while($done = curl_multi_info_read($master)) {
$info = curl_getinfo($done['handle']);
if ($info['http_code'] == 200) {

$content = curl_multi_getcontent($done['handle']);
$callback($content);

//新建一个curl资源并加入并发队列
if($i < sizeof($urls)) {
$arr_chs[$urls[$i]] = curl_init();
$options[CURLOPT_URL] = $urls[$i]; // increment i
curl_setopt_array($arr_chs[$urls[$i]], $options);
curl_multi_add_handle($master, $arr_chs[$urls[$i]]);
}

curl_multi_remove_handle($master, $done['handle']);
curl_close($done['handle']);
} else {

echo curl_errno($done['handle']),":",curl_error($done['handle']),"\n";
}
}
} while($running);

curl_multi_close($master);
return true;
}

大型Web应用运行时 PHP负载均衡指南

      过去当运行一个大的web应用时候意味着需要运行一个大型的web服务器。因为你的应用吸引了大量的用户,你将不得不在你的服务器里增加更多的内存和处理器。今天,“大型服务器”模式已经过去,取而代之的是大量的小服务器,使用各种各样的负载均衡技术。

       “更多小服务器”的优势超过过去的“大型服务器”模式体现在两个方面:

       1. 如果服务器宕机,那么负载均衡系统将停止请求到宕机的服务器,转而分发负载到其他正常运行的服务器上。
2. 扩展你的服务器更加容易。你要做的仅仅是加入新的服务器到负载均衡系统。不需要中断你的应用运行。
所以,把握住这个机会。当然,代价就是这要求你的应用开发时增加一点复杂度。这就是本文要覆盖的内容。

       这时你可能对自己说:“但是我怎么知道我正在使用负载均衡呢?”。最诚实的回答是,如果你正在问这个问题,那么答案是你多半没有在使用负载均衡系统并且你的系统不需要考虑这个问题。大多数情况,当应用成长足够大的规模时,负载均衡就需要明确提出和设置了。然而,我也偶尔看见虚拟主机公司为客户的应用做这个负载均衡,或者像下面描述的那样要自己来做。

        注意,我一直提“web应用”而不是website,这是想区分“web应用”是那些复杂的站点往往涉及服务器端编程和数据库,而不是website那样只显示简单的静态内容。

       1. PHP文件

       第一个问题是,如果你有大量的小型服务器,你怎么把你的php文件上传到所有的服务器上?有如下的方法供你参考:

       ◆分别上传所有的文件到每一个服务器 , 这种方法带来的问题是:想像一下你有20个服务器,那么上传过程中这将很容易导致错误,并且更新时极有可能导致不同服务器上有不同版本的文件。

       ◆使用 ‘rsync ‘ (或类似的软件) . 这样的工具能同步本地目录和多个远程主机目录上的文件。

       ◆使用版本控制软件(如subversion ) . 这是我最喜欢的方法。用它可以很好地维护我得代码,当发布我的应用时,可以在每一个服务器上运行svn update命令同步。这种方法也使切换服务器得代码到过去的某一个版本更加容易。

       ◆使用一个文件服务器(你可能发现NFS 非常适合做这件事情). 这种方式是使用一个文件服务器来存放你的web应用. 当然,如果你的文件服务器宕机,那么多所有你的站点将不能使用。这时,你就需要花费更多的开支来恢复它。

       选择哪种方式依赖于你的需求和你掌握的技能。如果你使用版本控制系统,那么你可能得计划一个方法如果同时执行一个更新命令更新所有服务器上的代码。然而,如果使用文件服务器,你就要实现一些失败恢复机制,防止万一服务器宕机导致请求失败。

       2. 文件上传

       当只有一台服务器时,文件上传不是一个问题。但是当我们有多台服务器时,那么上传的文件应该怎么存放呢?上传文件的问题和跨服务器php文件存储是类似的。下面是几种可能的方案:

       ◆把文件存储到数据库中。大多数数据允许存储二进制数据。当你请求文件下载时,访问数据把二进制数据和相应的文件名和类型输出给用户。在使用这种方案前应该考虑数据库怎样存储你的文件。该方法的问题在于如果数据库服务器宕机将使文件不可用。

       ◆在一个文件服务器上存储上传的文件 . 与前面的介绍一样,你要安装一个文件服务器让所有web服务器共享,把所有上传的文件上传到这里,上传后所有的web服务器就都可以使用它。但是,如果文件服务器宕机,那么可能发生图像文件下载中断。

       ◆设计你自己的上传机制传输文件到服务器到每一个服务器 . 这个方法没有单个文件服务器或者数据库方案的缺陷,但是将增加你代码的复杂度。例如,如果上传到多个服务器过程中,服务器宕机,你要怎么处理?

       用数据库存储上传文件但是设计一个文件缓存机制是一个不错的方案。当服务器接收一个文件下载请求时,首先检查缓存系统中是否有该文件,如果发现那么从缓存系统下载,否则从数据库读取并把它缓存到文件系统中。

       3. 会话(Sessions)

       如果你熟悉php的session 处理,你将可能知道默认情况下,它存储session数据在服务器的临时文件里。而且,这个文件仅仅在你请求处理的那个服务器上,但是接下来的请求可能被另外一个服务器处理,这将在另一个服务器上生成新的session。这导致session频繁地不被识别,如登录用户总是要求重新登录。

       我推荐的方案是,要么重新php内建的session处理机制存储session数据到数据库,或者实现你自己的机制保证发送一个用户的请求到同一台服务器。

       4. 配置(Configuration)

       尽管这个话题不是和php特别相关,我感觉还是有必要提及。当运行集群服务器时,用某种方法保持服务器之间的配置文件同步是一个好主意。如果配置文件不一致,可能导致一些非常奇怪的断断续续的行为导致很难排查这些问题。

       我推荐使用版本控制系统单独管理他们。这样你可以为不同的项目安装存储不同的php配置文件,也可以保持所有服务器配置文件同步。

       5. 日志(Logging)

       像配置问题一样,logging不是仅仅和php相关。但是对于保持服务器健康运行它仍然是非常重要的。没有正确的logging系统,你怎么知道如果PHP代码开始产生错误(在系统正式运行时,你总是关闭display_errors 设置,不是吗?)

       有几种方法你可以实现logging:

       1. 在每一个服务器上记录日志。 这是最简单的方法。每一个机器仅仅记录一个文件。好处是简单,可能只要很少的配置。但是,随着服务器数量的增多,监控每台服务器上的日志文件将变得非常困难。

       2. 记录日志到一个共享 这种方法每一个服务器仍然有这个日志文件,但是他们通过共享机制被存储在一个中央文件服务器上,这将使监控日志变得更简单。该方案的问题在于,如果文件服务器不可用将导致一个简单的日志不能写入问题最终导致整个应用崩溃。

       3. 记录日志到logging服务器 你可以使用一个logging软件,如syslog 来把所有的日志写到一个中央服务器。尽管这个方法要求更多的配置,但是他也提供了最健壮的方案。

PHP 运行原理

PHP的运行原理

1、我们从未手动启动过PHP的相关进程, 它是随着Apache的启动而运行的。

2、PHP通过mod_php5.so模块和Apache相连(具体来说是SAPI,即服务器应用程序编程接口)。

3、PHP总共有三个模块:内核、Zend引擎、以及类扩展。

4、PHP内核用来处理请求、文件流、错误处理等相关操作。

5、Zend引擎(ZE)用以将源文件转换成机器语言,然后在虚拟机上运行它。

6、扩展层是一组函数、类库和流,PHP使用它们来执行一些特定的操作。比如,我们需要MySQL扩展来连接到MySQL据库。

7、当ZE执行程序时可能需要连接若干扩展,这时ZE将控制权交给扩展,等处理完特定任务后再返回。

8、最后ZE将程序运行结果返回给PHP内核,PHP内核再将结果传送给SAPI层,最终输出到浏览器上。

在这里我们再深入探讨一下

1、Apache启动后, PHP解释程序也随之启动;

2、PHP的启动过程有两步;

     第一步是初始化一些环境变量,这将在整个SAPI生命周期中发生作用;

     此步操作在任何请求到达之前执行,启动Apache后,PHP解释程序也随之启动;PHP调用各个扩展的MINIT方法,从而使这些扩展切换到可用状态。MINIT的意思是“模块初始化”。

     第二步是生成只针对当前请求的一些变量设置;

     当一个页面请求发生时,SAPI层将控制权交给PHP层。于是PHP设置了用于回复本次请求所需的环境变量。同时,它建立一个变量表,用来存放执行过程中产生的变量名和值。PHP调用各个模块的RINIT方法,即“请求初始化”。一个经典的例子是Session模块的RINIT方法,如果在php.ini中启用了Session模块,那在调用该模块的RINIT方法就会初始化$_SESSION变量,并将相关内容读入。(RINIT方法可以看做一个准备过程,在程序执行期间就会自动启动。

3、PHP关闭关闭也分为两步:

      第一步:一旦一个页面执行完毕(无论执行到文件末尾还是遇到exit或die函数终止), PHP就会启动清理程序。它会按照顺序调用各个模块的RSHUTDOWN方法。(RSHUTDOWN方法用以清楚程序运行时产生的符号表,也就是对每个变量调用UNSET函数)

      第二部:最后所有的请求都已经处理完毕,SAPI也准备关闭了,PHP开始发行第二步。PHP调用每个扩展的MSHUTDOWN方法,这是各个模块最后一次释放内存的机会。

注意:只有在服务器没有请求的情况下才会执行“启动第一步”和“关闭第二步”。

YAF说明文档

Yaf - Yet Another Framework

Build Status
PHP framework written in c and built as a PHP extension.

Requirement

  • PHP 5.2 +

Install

Install Yaf

Yaf is an PECL extension, thus you can simply install it by:

$pecl install yaf

Compile Yaf in Linux

$/path/to/phpize
$./configure --with-php-config=/path/to/php-config
$make && make install

For windows

Yaf binary dlls could be found at http://code.google.com/p/yafphp/downloads/list

Document

Yaf manual could be found at: http://www.php.net/manual/en/book.yaf.php

IRC

efnet.org #php.yaf

For IDE

you could find a documented prototype script here: https://github.com/elad-yosifon/php-yaf-doc

Tutorial

layout

PHP 之 CURL学习(一)

  • 此文件为curl学习例子,参考http://www.chinaz.com/program/2010/0119/104346.shtmlPHP手册
  • libcurl 目前支持http、https、ftp、gopher、telnet、dict、file和ldap协议。
  • libcurl 同时也支持HTTPS认证、HTTP POST、HTTP PUT、FTP上传、HTTP基于表单的上传、代理、COOKIES和用户名+密码的验证

Example1

  • cURL的请求的基本步骤:
    1.初始化
    2.设置变量
    3.执行并获取结果
    4.释放cURL句柄
$ch = curl_init(); //1、初始化
curl_setopt($ch, CURLOPT_URL, 'http://www.baidu.com'); //2、设置选项、包括URL
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //CURLOPT_RETURNTRANSFER 将curl_exec()获取的信息以流文件形式返回,而不是直接输出
curl_setopt($ch, CURLOPT_HEADER, 1); //CURLOPT_HEADER 启用时会将头文件的信息作为数据流输出
$output = curl_exec($ch); //3、执行 curl_exec失败时返回flase,判断时应该用===
curl_close($ch); //4、释放句柄

Example2

  • 写两个简单的函数用curl来发送POST和GET请求
    curl_setopt_array() 为cURL传输回话批量设置选项
/**
*Send a POST request using cURL
*@param string $url to request
*@param array $post values to send
*@param array $options for url
*/
function curl_post($url, array $post = NULL, array $options = array()) {
$defaults = array(
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_URL => $url,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_TIMEOUT => 4,
CURLOPT_POSTFIELDS => http_build_query($post)
);

$ch = curl_init();
curl_setopt_array($ch, $options + $defaults);//数组相加 根据key将在后一个数组而不再前一个数组中的item加入第一个数组中(任意一个数组不是数组导致Faltal error)
if(false === $result = curl_exec($ch)) {
trigger_error(curl_error($ch));
}
curl_close($ch);
return $result;
}

/**
*Send a GET request using cURL
*@param string $url to request
*@param array $get values to send
*@param array $options for cURL
*/
function curl_get($url, array $get = NULL, array $options = array()) {
$defaults = array(
CURLOPT_URL => $url.($get ? (strpos($url, '?') === false ? '?' : '&').http_build_query($get) : ''),
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 4
);

$ch = curl_init();
curl_setopt_array($ch, $options + $defaults);
if(false === $result = curl_exec($ch)) {
trigger_error(curl_error($ch));
}
curl_close($ch);
return $result;
}

Example3

  • 通过cURL上传文件
$url = 'http://localhost/log.php';
$post_data = array(
'name' => 'myname',
'file' => '@d:\test.jpg' //上传的本地文件要加@符号
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1); //可有可无
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
if(false === $result = curl_exec($ch)) {
trigger_error(curl_error($ch));
}
curl_close($ch);