php-fpm - 启动参数及重要配置详解

约定几个目录
/usr/local/php/sbin/php-fpm
/usr/local/php/etc/php-fpm.conf
/usr/local/php/etc/php.ini

一,php-fpm的启动参数

#测试php-fpm配置
/usr/local/php/sbin/php-fpm -t
/usr/local/php/sbin/php-fpm -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf -t

#启动php-fpm
/usr/local/php/sbin/php-fpm
/usr/local/php/sbin/php-fpm -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf

#关闭php-fpm
kill -INT `cat /usr/local/php/var/run/php-fpm.pid`

#重启php-fpm
kill -USR2 `cat /usr/local/php/var/run/php-fpm.pid`

二,php-fpm.conf重要参数详解

pid = run/php-fpm.pid
#pid设置,默认在安装目录中的var/run/php-fpm.pid,建议开启

error_log = log/php-fpm.log
#错误日志,默认在安装目录中的var/log/php-fpm.log

log_level = notice
#错误级别. 可用级别为: alert(必须立即处理), error(错误情况), warning(警告情况), notice(一般重要信息), debug(调试信息). 默认: notice.

emergency_restart_threshold = 60
emergency_restart_interval = 60s
#表示在emergency_restart_interval所设值内出现SIGSEGV或者SIGBUS错误的php-cgi进程数如果超过 emergency_restart_threshold个,php-fpm就会优雅重启。这两个选项一般保持默认值。

process_control_timeout = 0
#设置子进程接受主进程复用信号的超时时间. 可用单位: s(秒), m(分), h(小时), 或者 d(天) 默认单位: s(秒). 默认值: 0.

daemonize = yes
#后台执行fpm,默认值为yes,如果为了调试可以改为no。在FPM中,可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置。

listen = 127.0.0.1:9000
#fpm监听端口,即nginx中php处理的地址,一般默认值即可。可用格式为: 'ip:port', 'port', '/path/to/unix/socket'. 每个进程池都需要设置.

listen.backlog = -1
#backlog数,-1表示无限制,由操作系统决定,此行注释掉就行。backlog含义参考:http://www.3gyou.cc/?p=41

listen.allowed_clients = 127.0.0.1
#允许访问FastCGI进程的IP,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接

listen.owner = www
listen.group = www
listen.mode = 0666
#unix socket设置选项,如果使用tcp方式访问,这里注释即可。

user = www
group = www
#启动进程的帐户和组

pm = dynamic #对于专用服务器,pm可以设置为static。
#如何控制子进程,选项有static和dynamic。如果选择static,则由pm.max_children指定固定的子进程数。如果选择dynamic,则由下开参数决定:
pm.max_children #,子进程最大数
pm.start_servers #,启动时的进程数
pm.min_spare_servers #,保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
pm.max_spare_servers #,保证空闲进程数最大值,如果空闲进程大于此值,此进行清理

pm.max_requests = 1000
#设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 '0' 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0.

pm.status_path = /status
#FPM状态页面的网址. 如果没有设置, 则无法访问状态页面. 默认值: none. munin监控会使用到

ping.path = /ping
#FPM监控页面的ping网址. 如果没有设置, 则无法访问ping页面. 该页面用于外部检测FPM是否存活并且可以响应请求. 请注意必须以斜线开头 (/)。

ping.response = pong
#用于定义ping请求的返回相应. 返回为 HTTP 200 的 text/plain 格式文本. 默认值: pong.

request_terminate_timeout = 0
#设置单个请求的超时中止时间. 该选项可能会对php.ini设置中的'max_execution_time'因为某些特殊原因没有中止运行的脚本有用. 设置为 '0' 表示 'Off'.当经常出现502错误时可以尝试更改此选项。

request_slowlog_timeout = 10s
#当一个请求该设置的超时时间后,就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 '0' 表示 'Off'

slowlog = log/$pool.log.slow
#慢请求的记录日志,配合request_slowlog_timeout使用

rlimit_files = 1024
#设置文件打开描述符的rlimit限制. 默认值: 系统定义值默认可打开句柄是1024,可使用 ulimit -n查看,ulimit -n 2048修改。

rlimit_core = 0
#设置核心rlimit最大限制值. 可用值: 'unlimited' 、0或者正整数. 默认值: 系统定义值.

chroot =
#启动时的Chroot目录. 所定义的目录需要是绝对路径. 如果没有设置, 则chroot不被使用.

chdir =
#设置启动目录,启动时会自动Chdir到该目录. 所定义的目录需要是绝对路径. 默认值: 当前目录,或者/目录(chroot时)

catch_workers_output = yes
#重定向运行过程中的stdout和stderr到主要的错误日志文件中. 如果没有设置, stdout 和 stderr 将会根据FastCGI的规则被重定向到 /dev/null . 默认值: 空.

三,常见错误及解决办法整理

1,request_terminate_timeout的值如果设置为0或者过长的时间,可能会引起file_get_contents的资源问题。
如果file_get_contents请求的远程资源如果反应过慢,file_get_contents就会一直卡在那里不会超时,我们知道php.ini 里面max_execution_time 可以设置 PHP 脚本的最大执行时间,但是,在 php-cgi(php-fpm) 中,该参数不会起效。真正能够控制 PHP 脚本最大执行时间的是 php-fpm.conf 配置文件中的request_terminate_timeout参数。

request_terminate_timeout默认值为 0 秒,也就是说,PHP 脚本会一直执行下去。这样,当所有的 php-cgi 进程都卡在 file_get_contents() 函数时,这台 Nginx+PHP 的 WebServer 已经无法再处理新的 PHP 请求了,Nginx 将给用户返回“502 Bad Gateway”。修改该参数,设置一个 PHP 脚本最大执行时间是必要的,但是,治标不治本。例如改成 30s,如果发生 file_get_contents() 获取网页内容较慢的情况,这就意味着 150 个 php-cgi 进程,每秒钟只能处理 5 个请求,WebServer 同样很难避免"502 Bad Gateway"。解决办法是request_terminate_timeout设置为10s或者一个合理的值,或者给file_get_contents加一个超时参数。

$ctx = stream_context_create(array(  
   'http' => array(  
       'timeout' => 10 //设置一个超时时间,单位为秒  
       )  
   )  
);  
file_get_contents($str, 0, $ctx);  

2,max_requests参数配置不当,可能会引起间歇性502错误:
http://hily.me/blog/2011/01/nginx-php-fpm-502/

pm.max_requests = 1000
#设置每个子进程重生之前服务的请求数. 对于可能存在内存泄漏的第三方模块来说是非常有用的. 如果设置为 '0' 则一直接受请求. 等同于 PHP_FCGI_MAX_REQUESTS 环境变量. 默认值: 0.
这段配置的意思是,当一个 PHP-CGI 进程处理的请求数累积到 500 个后,自动重启该进程。

但是为什么要重启进程呢?

一般在项目中,我们多多少少都会用到一些 PHP 的第三方库,这些第三方库经常存在内存泄漏问题,如果不定期重启 PHP-CGI 进程,势必造成内存使用量不断增长。因此 PHP-FPM 作为 PHP-CGI 的管理器,提供了这么一项监控功能,对请求达到指定次数的 PHP-CGI 进程进行重启,保证内存使用量不增长。

正是因为这个机制,在高并发的站点中,经常导致 502 错误,我猜测原因是 PHP-FPM 对从 NGINX 过来的请求队列没处理好。不过我目前用的还是 PHP 5.3.2,不知道在 PHP 5.3.3 中是否还存在这个问题。

目前我们的解决方法是,把这个值尽量设置大些,尽可能减少 PHP-CGI 重新 SPAWN 的次数,同时也能提高总体性能。在我们自己实际的生产环境中发现,内存泄漏并不明显,因此我们将这个值设置得非常大(204800)。大家要根据自己的实际情况设置这个值,不能盲目地加大。

话说回来,这套机制目的只为保证 PHP-CGI 不过分地占用内存,为何不通过检测内存的方式来处理呢?我非常认同高春辉所说的,通过设置进程的峰值内在占用量来重启 PHP-CGI 进程,会是更好的一个解决方案。

3,php-fpm的慢日志,debug及异常排查神器:
request_slowlog_timeout设置一个超时的参数,slowlog设置慢日志的存放位置,tail -f /var/log/www.slow.log即可看到执行过慢的php过程。
大家可以看到经常出现的网络读取超过、Mysql查询过慢的问题,根据提示信息再排查问题就有很明确的方向了。

php运行jsv8引擎

这两天在写markdown2html的功能,现有的ph扩展转换质量都很差,发现有一个to-markdown.js转换效果还算能满足需求,于是折腾了一个在php里运行v8引擎的小工具,写api来处理。

看到红薯有个翻译了一个demo:http://www.oschina.net/question/12_62525

Ubuntu 12.04下安装,部署到centos上的话,应该一样,但还没有测试

sudo apt-get install php5-dev php-pear libv8-dev build-essential
sudo pecl install v8js
sudo pecl install channel://pecl.php.net/v8js-0.1.3
sudo echo extension=v8js.so >>/etc/php5/cli/php.ini
sudo echo extension=v8js.so >>/etc/php5/apache2/php.ini
php -m | grep v8
<?php
$markdown = 'to-markdown.js';
$markdown = file_get_contents($markdown);

$html = 'test.html';
$html = file_get_contents($html);
$html = str_replace("\r", '_+-r-+_', $html);
$html = str_replace("\n", '_+-n-+_', $html);
$html = addslashes($html);

$v8 = new V8Js();
$JS = <<< EOT
{$markdown}
var html= '{$html}';
toMarkdown(html);
EOT;

file_put_contents('my.js', $html);
$return = $v8->executeString($JS);

$return = str_replace('_+-r-+_', "\r", $return);
$return = str_replace("_+-n-+_", "\n", $return);
file_put_contents('result.txt', $return);

测试demo如下:
html2markdown

php中curl的多线程

我刚刚才知道php的curl居然有多线程版,以后的采集过程就不需要再使用Python的多线程咯。

许多人对php手册中语焉不详的curl_multi一族的函数头疼不已,它们文档少,给的例子 更是简单的让你无从借鉴,我也曾经找了许多网页,都没见一个完整的应用例子。
# curl_multi_add_handle
# curl_multi_close
# curl_multi_exec
# curl_multi_getcontent
# curl_multi_info_read
# curl_multi_init
# curl_multi_remove_handle
# curl_multi_select

一般来说,想到要用这些函数时,目的显然应该是要同时请求多个url,而不是一个一个依次请求,否则不如自己循环去调curl_exec好了。

步骤总结如下:

第一步:调用curl_multi_init
第二步:循环调用curl_multi_add_handle
这一步需要注意的是,curl_multi_add_handle的第二个参数是由curl_init而来的子handle。
第三步:持续调用curl_multi_exec
第四步:根据需要循环调用curl_multi_getcontent获取结果
第五步:调用curl_multi_remove_handle,并为每个字handle调用curl_close
第六步:调用curl_multi_close

这里有一个网上找的简单例子,其作者称为dirty的例子,(稍后我会说明为何dirty):
/*
Here's a quick and dirty example for curl-multi from PHP, tested on PHP 5.0.0RC1 CLI / FreeBSD 5.2.1
*/

$connomains = array(
"http://www.cnn.com/",
"http://www.canada.com/",
"http://www.yahoo.com/"
);

$mh = curl_multi_init();

foreach ($connomains as $i => $url) {
$conn[$i]=curl_init($url);
curl_setopt($conn[$i],CURLOPT_RETURNTRANSFER,1);
curl_multi_add_handle ($mh,$conn[$i]);
}

do { $n=curl_multi_exec($mh,$active); } while ($active);

foreach ($connomains as $i => $url) {
$res[$i]=curl_multi_getcontent($conn[$i]);
curl_close($conn[$i]);
}

print_r($res);

整个使用过程差不多就是这样,但是,这个简单代码有个致命弱点,就是在do循环的那段,在整个url请求期间是个死循环,它会轻易导致CPU占用100%。

现在我们来改进它,这里要用到一个几乎没有任何文档的函数curl_multi_select了,虽然C的curl库对select有说明,但是,php里的接口和用法确与C中有不同。

把上面do的那段改成下面这样:
do {
$mrc = curl_multi_exec($mh,$active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active and $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}

因为$active要等全部url数据接受完毕才变成false,所以这里用到了curl_multi_exec的返回值判断是否还有数据,当有数据的时候就不停调用curl_multi_exec,暂时没有数据就进入select阶段,新数据一来就可以被唤醒继续执行。这里的好处就是CPU的无谓消耗没有了。

另外:还有一些细节的地方可能有时候要遇到:

控制每一个请求的超时时间,在curl_multi_add_handle之前通过curl_setopt去做:
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);

判断是否超时了或者其他错误,在curl_multi_getcontent之前用:curl_error($conn[$i]);

孤魂写的一个多线程的函数版:

<?php
$text = remote(array('http://www.kalvin.cn/about-me/','http://www.kalvin.cn/'));
print_r($text);

function remote($urls) {
    if (!is_array($urls) or count($urls) == 0) {
        return false;
    } 

    $curl = $text = array();
    $handle = curl_multi_init();
    foreach($urls as $k => $v) {
        $nurl[$k]= preg_replace('~([^:\/\.]+)~ei', "rawurlencode('\\1')", $v);
        $curl[$k] = curl_init($nurl[$k]);
        curl_setopt($curl[$k], CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl[$k], CURLOPT_HEADER, 0);
        curl_multi_add_handle ($handle, $curl[$k]);
    } 

    $active = null;
    do {
        $mrc = curl_multi_exec($handle, $active);
    } while ($mrc == CURLM_CALL_MULTI_PERFORM);

    while ($active && $mrc == CURLM_OK) {
        if (curl_multi_select($handle) != -1) {
            do {
                $mrc = curl_multi_exec($handle, $active);
            } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        } 
    } 

    foreach ($curl as $k => $v) {
        if (curl_error($curl[$k]) == "") {
        $text[$k] = (string) curl_multi_getcontent($curl[$k]); 
        }
        curl_multi_remove_handle($handle, $curl[$k]);
        curl_close($curl[$k]);
    } 
    curl_multi_close($handle);
    return $text;
}

一个多线程的采集类
https://github.com/xfeng/MultiHttpRequest

<?php
/*
	PHP通用采集
	支持多列表匹配;
	页面内容压缩后匹配,降低规则难度;
	多线程采集,速度更快,为防止目标站限制注意设置并发数量;
	采集后导出content里的内容和images文件夹中图片即可;
	echo str_pad(1,8,"0",STR_PAD_LEFT);00000001
*/
header("Content-type: text/html;charset=utf-8");

set_time_limit(0);

require("libs/class_curl_multi.php");

//连接数据库
$link = mysql_connect("127.0.0.1:3306","root","");
mysql_select_db("test",$link);

//清空数据库
mysql_query("TRUNCATE TABLE content");

//域名前缀
$base = "http://sellbest.net";

//需要采集的规则列表(分页)
$list = array(
	//'http://sellbest.net/by-brand/page[1-14]/1-PRADA.html',
	'http://sellbest.net/by-brand/limit1800/page[1-1]/1-PRADA.html',
);

//在列表页面内容链接表达式
$list_rules = '<p class="productName"><a href="(.*)">.*</a>';

//内容页面信息字段表达式
$detail_rules = array(
	'meta_title'=>'<title>(.*)</title>',
	'meta_keywords'=>'<meta name="keywords" content="(.*)" />',
	'meta_description'=>'<meta name="description" content="(.*)" />',
	'product_name'=>'<h4 class="h4-title float-l"> (.*)</h4>',
	'product_image'=>'<div class="v-inner">.*<a href="(.*)" id="originalImg"><img src=".*" alt=".*" /></a>.*</div>',
	'product_price'=>'Our Price : <strong>(.*)</strong>',
	'product_description'=>'<div class="description-text" id="description"><div class="border-cont">(.*)</div>',
);

//实例
$mp = new MultiHttpRequest();

//调试使用记录采集条目
$j = 1;

//每次并发几个链接
$limit = 10;

//开始采集
foreach ($list as $link) {
	
	//解析列表页数
	preg_match_all('#\[(.*)\]#isU',$link,$_page);
	if($_page[1][0]==''){
		continue;
	}
	$pages = explode('-',$_page[1][0]);
	if(count($pages) != 2){
		continue;
	}
	
	$urls = array();
	
	for($i=$pages[0];$i< =$pages[1];$i++){
		if(count($urls) < $limit){
			$urls[] = preg_replace('#\[(.*)\]#isU',$i,$link);
			if($i != $pages[1]){
				continue;
			}
		}
		//采集列表内容
		$mp->set_urls($urls);
		$contents = $mp->start();
				
		foreach ($contents as $content) {
			
					$content = _prefilter($content);
					//debug
					//exit($content);
					
					//匹配内容
					preg_match_all('#'.addslashes($list_rules).'#isU',$content,$pregArr);
					
					$detail_urls = array();
					foreach($pregArr[1] as $detail_key=>$detail_value){
					 	$data = array();
						if(count($detail_urls) < $limit ){
								$detail_urls[] = $base.$detail_value;
								if($pregArr[1][$detail_key+1] != ''){
									continue;
								}
						}
						
						//print_r($detail_urls);
						//continue;						
						$mp->set_urls($detail_urls);
						
						$details = $mp->start();
						//图片路径临时存放
						$images_urls = array();
						
						//采集内容页面
						foreach ($details as $detail) {
							$detail = _prefilter($detail);
							//debug
							exit($detail);
							
							foreach ($detail_rules as $key => $value) {
								
								preg_match_all('#'.addslashes($value).'#isU',$detail,$detailArr);
								//处理特殊这段信息
								switch ($key) {
									case 'product_image':
										$data[$key] = "images/".md5($detailArr[1][0]).".jpg";
										if(!file_exists($data[$key])){
											$images_urls[$data[$key]] = $base.$detailArr[1][0];
											//file_put_contents($data[$key],$mp->get_content($base.$detailArr[1][0]));
										}
										break;
									case 'product_description':
										$data[$key] = trim(strip_tags($detailArr[1][0]));
										break;
									default:
										$data[$key] = $detailArr[1][0];
										break;
								}
												
							}
							
							
							//产品url			
							$data['product_url'] = _title($data['product_name']);
							//转义采集后的数据
							foreach ($data as $_k => $_v) {
								$data[$_k] = addslashes($_v);
							}
							//入库
							$r = mysql_query("
							insert into `content` values(
							null,
							'{$data['meta_title']}',
							'{$data['meta_keywords']}',
							'{$data['meta_description']}',
							'{$data['product_name']}',
							'{$data['product_image']}',
							'{$data['product_price']}',
							'{$data['product_description']}',
							'{$data['product_url']}')");
							//打印log
							_flush($j++."|".$r."|".$data['product_name']."\n");
							//_flush($data);
						}
						//远程图片本地化
						$mp->set_urls($images_urls);						
						$images = $mp->start();						
						foreach ((array)$images as $image_key => $image_value) {
							_flush($image_key."\n");
							file_put_contents($image_key,$image_value);
						}
						//清空内容url并加入本次循环url。不然本次会被跳过
						$detail_urls = array($base.$detail_value);
			}
		}
		//清空内容url并加入本次循环url。不然本次会被跳过
		$urls = array(preg_replace('#\[(.*)\]#isU',$i,$link));
	}
}

//=========================================================
function _flush($msg)
{
	print_r ($msg);
	ob_flush();
	flush();
}


function _title($title, $separator = '-' )
{
	$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', strtolower($title));
	$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
	return trim($title, $separator);
}


function _prefilter($output) {
	$output=preg_replace("/\/\/[\S\f\t\v ]*?;[\r|\n]/","",$output);
	$output=preg_replace("/\< \!\-\-[\s\S]*?\-\-\>/","",$output);
	$output=preg_replace("/\>[\s]+\< ",$output);
	$output=preg_replace("/;[\s]+/",";",$output);
	$output=preg_replace("/[\s]+\}/","}",$output);
	$output=preg_replace("/}[\s]+/","}",$output);
	$output=preg_replace("/\{[\s]+/","{",$output);
	$output=preg_replace("/([\s]){2,}/","$1",$output);
	$output=preg_replace("/[\s]+\=[\s]+/","=",$output);
	return $output;
}
?>

PHP5.3/5.4安装Zend Optimizer(Zend Guard Loader)[Linux][Windows]

updated 2013-06-25:

php5.4安装Zend Guard Loader方法一样,只是下载路径变了而已
wget http://downloads.zend.com/guard/6.0.0/ZendGuardLoader-70429-PHP-5.4-linux-glibc23-x86_64.tar.gz
目前还没有看到php5.5 Zend Guard Loader的下载路径。

到目前发稿截止,Zend Optimizer目前的版本为3.3.3(Windows)、3.3.9(Linux),且均不支持PHP5.3及以上版本。

很多同学包括我自己,一直也是被B的只能使用PHP5.2版本,当然,因为DEDE对PHP5.3的支持不好,也没办法,只能停在5.2时代。

根据官方的说法,一直是这样:由于PHP5.3的升级功能太多,ZP更新以后,也无法再往下兼容低版本的Zeng Guard加密的PHP文件,甚至不支持PHP5.2.13以下版本的PHP环境。

总结成一句话,那就是“PHP版本更新太快了,已经没必要向下兼容低版本的Zend Guard和PHP5.2及以下版本了”。就如同PHP5.3不能兼容PHP5.2一样,很多函数被取消,很多同学认为这是PHP5时代和PHP6时代的分水岭。

PHP6就要来了。

话说了这么多,最重要的一点,其实PHP5.3也可以安装Zend Optimizer的。因为Zend公司专门针对5.3版本出了专用版本。那就是传说中的“Zend Guard Loader (Runtime for PHP 5.3)”

目前推测这个版本只能用于5.3系列,其官方网站已经明确列出5.2和5.3系的下载地址:http://www.zend.com/en/products/guard/downloads。

PHP5.3安装Zend Optimizer,已经被正式改成了“Zend Guard Loader”,简单来说,就是不需要安装软件,而是直接在php.ini中调用组件(扩展)即可。 继续阅读PHP5.3/5.4安装Zend Optimizer(Zend Guard Loader)[Linux][Windows]

IP地址API

当我们开发了一些应用以后,可能需要限制用户程序进行分发,典型的例子就是绑定域名的App。如果用户将域名使用本地HOST解析,很有可能绕过程序的域名限制,从而绑定其它域名进行产品分发。

使用本地和服务器端IP同步验证,则可以很好的解决这个问题。

如果,使用gethostbyname取得一个域名的IP,同时再去访问一下其它外网,取得程序所在外网IP,如果两个IP不一样,则可以判断出程序的合法性。

从一些大网站取得IP是比较可行的办法。 继续阅读IP地址API

一些PHP+PDO的查询技巧

1,类似PHP框架中的预处理,简单理解为可绑定参数自动传值

This example fetches data based on a key value supplied by a form. The user input is automatically quoted, so there is no risk of a SQL injection attack.

这个例子里获取的数据,根据用户提交的表单自动生成,不需要程序员判断变量,用户提交的数据将被自动转义,所有没有SQL注入的问题


<?php
$stmt = $dbh->prepare("SELECT * FROM users where name = ?");
if ($stmt->execute(array($_GET['name']))) {
while ($row = $stmt->fetch()) {
print_r($row);
}
}
?>

2,同上,自动赋值的另外一种代码样式


<?php
$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1, $name);
$stmt->bindParam(2, $value);

// insert one row
$name = 'one';
$value = 1;
$stmt->execute();

// insert another row with different values
$name = 'two';
$value = 2;
$stmt->execute();
?>

3,PHP+PDO+Mysql

<?php
try {
   $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
   foreach ($dbh->query('SELECT * from FOO') as $row) {
      print_r($row);
   }
   $dbh = null;
} catch (PDOException $e) {
   print "Error!: " . $e->getMessage() . "<br/>";
   die();
}
?>

4,完善的事务处理功能

<?php
try {
  $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2',
      array(PDO::ATTR_PERSISTENT => true));
  echo "Connected\n";
  $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $dbh->beginTransaction();
  $dbh->exec("insert into staff (id, first, last) values (23, 'Joe', 'Bloggs')");
  $dbh->exec("insert into salarychange (id, amount, changedate)
      values (23, 50000, NOW())");
  $dbh->commit();

} catch (Exception $e) {
  $dbh->rollBack();
  echo "Failed: " . $e->getMessage();
}
?>

Windows+Apache+PHP安全配置和优化笔记

Apache在Linux下应用的比较多,这里的配置基于Windows NT,因为国内大多数站长都是使用的Windows服务器。

前段时间,我的手机壁纸网站服务器IIS崩溃,一时没有找到原因,重装IIS也没有解决问题,所以临时使用Apache做为WebServer。

这里记录下配置笔记。

首先是Apache的安全配置:

  1. 如果不需要目录浏览,禁用目录浏览
    Options FollowSymLinks
  2. 如果不需要.htaccess功能,能起到非常重要的安全保护并加快程序响应速度
    AllowOverride none
  3. 禁用不用或者不安全的模块
  4. 启用Apache的错误日志功能
    ErrorLog "D:/wamp/logs/apache_error.log"
  5. 启用PHP的目录权限限制,严格控制跨目录访问权限
    php_admin_value open_basedir "E:/wamp/www.4wei.cn"
  6. 修改Apache的默认用户组和用户
    <IfModule !mpm_winnt_module>
    User daemon
    Group daemon
    </IfModule>
  7. 禁止显示Apache的版本信息和系统信息
    ServerTokens ProductOnly
    ServerSignature Off

其次,是Apache在Windows下的优化配置,主要是mpm_winnt的配置:

  1. 主要通过ThreadsPerChild和MaxRequestsPerChild两个参数来优化
    <IfModule mpm_winnt.c>
    ThreadsPerChild 250
    MaxRequestsPerChild 5000
    </IfModule>
  2. mpm_winnt的详细说明和配置请参考
    http://www.javatang.com/archives/2008/02/19/0801260.html

最后,是PHP的一些安全设置:

  1. 请保证你的PHP版本已经升级为最新版,这样可以解决很多棘手安全问题
  2. 可以考虑启用安全模式,如果程序没有严格要求的话
  3. 必须禁用一些高危函数,其中,可以保留phpinfo这个函数

    disable_functions = exec,passthru,shell_exec,system,popen,proc_open,proc_close,curl_exec,curl_multi_exec,parse_ini_file,show_source,dl,passthru,escapeshellarg,escapeshellcmd

  4. 禁用危险类

    disable_classes = "com"

  5. 禁止显示PHP的版本
    expose_php = Off
  6. 禁用全局变量,这是一个非常可怕的问题,PHP6中已经删除此项设置
    register_globals = Off

现在用Nginx的用户不少,权限配置不是很完善,可以参考下文:
http://www.xpb.cn/blog/665.html

让Snoopy也支持HTTPS的解决办法

How to make Snoopy work with https, we will find out!
Class Name:Snoopy.class.php
Snoopy version:1.2.4

如何让Snoopy这个PHP支持HTTPS,即SSL安全协议,我们今天就来解决这个问题。

First of all, look at the comment of $curl_path in Snoopy.class.php.

Snoopy will use cURL for fetching SSL content if a full system path to the cURL binary is supplied here.
set to false if you do not have cURL installed.
See http://curl.haxx.se for details on installing cURL.
Snoopy does *not* use the cURL library functions built into php,as these functions are not stable as of this Snoopy release.

首先,看一下 Snoopy.class.php 中的注释。

如果系统支持cURL binary的话,Snoopy 将使用cURL来获取SSL内容.
如果没有安装cURL扩展,那这里的$curl_path默认就是false.
如何安装cURL请参见:http://curl.haxx.se .
虽然PHP5版本中已经内置了cURL函数,但Snoopy的作者认为,本版Snoopy发布时,这些cURL库的性能还不是很稳定。

如此说来,Snoopy的移情别恋是因为有更好的扩展库推荐。这个cURL库就是:http://curl.haxx.se。
访问这个站点,我看到当前的cURL版本已经更新到 2010年2月的 7.20.0,我顺道去看了一下PHP5.2.11 ext目录下的cURL.dll,可惜看不到版本号。

我使用的开发环境是Wamp,本以为在Windows环境下无法使用cURL功能,但很高兴,我在下载页面:http://curl.haxx.se/download.html 找到了Win32版本。而且,版本还比较丰富,包括Generic、MSVC和64位的版本。
默认支持2003/XP,so,尚不清楚在Win7下是否支持。有测试过的朋友可以反馈一下。

按照说明,下载openSSL和zlib并安装,Linux下基本已经默认自带了,各位可以可以检查一下,没有的可以按照说明编译,并重新配置一下PHP。

最后,重新在Snoopy.class.php中,指定cURL路径,设置端口为443,问题解决。

php在线翻译类,基于Google翻译API开发(Google Translate API For PHP)

Google Translate API For PHP是一个基于Google Translate开发的翻译类,可以帮助PHP程序员实现多国文字的翻译。
本类使用到了Google的AJAX Translate API和Snoopy类,以模拟HTTP请求的方式实现数据的翻译。

以下是更新日志,如果您的接口不是最新版,请下载更新

  1. 2010-03,发布基本功能版
  2. 2010-06,1.0.0,封装成类咯
  3. 2010-10,1.0.1,更换翻译服务器,数据返回处理由JSON改成数组

以下是使用方法:

<?php
//demo 1
$Google = new GoogleTranslate();
$Google->text = '这是一个基于Google在线翻译的工具';
$Google->from = 'zh-CN';
$Google->to = 'en';
$Google->translate();
echo $Google->result;

//demo 2
$Google = new GoogleTranslate('这是一个基于Google在线翻译的工具', 'zh-CN', 'en');
echo $Google->result;

//demo 3
$Google = new GoogleTranslate();
$Google->translate('这是一个基于Google在线翻译的工具', 'zh-CN', 'en');
echo $Google->result;
?>

部份同学不知道这个类是做什么用的,以下给出一份实例运用代码,用户可以保存为query.php跑一跑。

<?php
if($_POST['text'])
{
	include('GoogleTranslate.class.php');
	
	$Google = new GoogleTranslate();
	$Google->text = $_POST['text'];
	$Google->from = 'zh-CN';
	$Google->to = 'en';
	$Google->translate();
	$content = $Google->result;
}

?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>采集进程控制台</title>
</head> 
<body>
<form method="post" action="">
	<textarea name="text" rows="5" cols="40"><?php
		echo $content ? $content : '请在这里输入要翻译的内容!';
	?></textarea><br />
	<input type="submit" />
</form>

</body>
</html>

以下是完整程序包:
本地下载:PHP翻译类
站长下载:PHP翻译类
A5下载:PHP翻译类

使用Snoopy下载论坛附件,并上传到网盘的实例

Snoopy无疑是我使用过的最强大的采集类,本博也做过一些简单的入门介绍。

近日,我完成了数个基于Snoopy的程序,如批量下载百度文档附件、论坛附件下载、本地文件批量上传,已经能比较熟练的使用此类。加上本博主要是做采集程序研究的,那么,有必要将Snoopy的一些高级功能拿出来晒晒,照顾下新人。那今天要演示的一个功能是,Snoopy模拟用户登陆论坛并下载附件,并上传到网盘的一个实例。

通过本例,我们要取得附件上传到网盘以后,返回附件在网盘的下载链接。这个功能,可以和火车采集器无缝结合,实现火车采集器文章、Snoopy下载附件并按自定义目录保存、附件同步上传的效果。

首先讲一下模拟用户登陆需要使用到的几个重要数据。

  1. 模拟一个浏览器,如Firefox 3.5,即User-agent;
  2. 登陆论坛的Cookie值;
  3. 一个referer,即来源页地址;

继续阅读使用Snoopy下载论坛附件,并上传到网盘的实例

zend guard 4/5 破解版和免过期办法,已补授权Key一枚,成功注册。

以下为本人收集的zend guard使用和注册方法。

zend guard 4 有一个特别版,但使用破解补丁以后无法正常完成加密,但加密后的文件是可以正常使用的。以下是下载地址,可以使用迅雷和电驴下载。

点此下载:Zend.Guard.v4.0.1.CRACKED.rar

zend guard 4加密过的程序文件已经能被反编译过来,而zend guard 5目前还依然坚挺,可惜没有大侠放出特别版,买不起zend guard商业服务的个人和小公司几乎可以说是望尘莫及,以下是网上找到的一个免去试用版加密的文件有14天过期的限制。

首先下载以下的安装文件,目前最新版本是5.5.0,包含windows和linux两个版本,均为32位。

http://downloads.zend.com/guard/5.5.0/ZendGuard-5_5_0.exe
http://downloads.zend.com/guard/5.5.0/ZendGuard-5_5_0.tar.gz

使用ZendGuard 5 加密前,修改系统时间到 2099-12-31日,再加密文件,这样就不会出加密后的文件14天后过期的问题了。

今日朋友送来一枚授权,额,终于按是注册成功了,不敢独享,免费发布下载了。

干什么?快下载吧:zend_guard 5能用的授权,解压后会得到一个.zl文件,打开ZendGuard,Help->register->seach license from disk,然后注册ok!这个授权文件到期时间是2010年7月10号,把自己电脑的日期调到这个时间之前就可以用。笑。

至于如何使用ZendGuard,以前转摘有一个视频教程:

使用zend_guard对PHP代码进行加密,视频教程

PHP Client Snoopy.class.php 的扩展优化

这几天在研究Snoopy的采集程序,完成对百度文档的采集和附件下载。

其中需要使用到对HTML进行DOM模型操作,读取表单中某个字段的Value,于是在网上找了一些类,发现了HTML SQL 和 DOMDocument两个类,功能还算是比较完善,可惜并没有我想要的效果,于是决定自己动手开发。

其中要加上自己的一些想法,在离开北京的最后一天,写上此文,估计年后就能看到成品了。希望这次回家不要太贪玩了。吼吼。 继续阅读PHP Client Snoopy.class.php 的扩展优化

实现PHP168图片模型的采集、发布、缩略图提取下载

使用火车采集器往PHP168发布数据,总有很多问题,从V5到现在的V6。

由于使用的人数并不多,这里就不多讲问题原因了。

以下是我的解决办法,简单思路为:

  1. 使用HTML代码的形式,发布图片内容,使用正则提取图片地址。
  2. 分析每条图片地址,如果是远程图片,就将图片下载到地。
  3. 检查缩略图是否存在,不存的话,自动从内容中提取缩略图。

继续阅读实现PHP168图片模型的采集、发布、缩略图提取下载