win2003下PHP使用preg_match_all导致apache崩溃问题的解决方法
程序员文章站
2022-06-20 17:59:08
小编的平台是windows server 2003(32位系统) + apache/2.2.9 (win32) + php/5.2.17,在使用正则表达式 preg_mat...
小编的平台是windows server 2003(32位系统) + apache/2.2.9 (win32) + php/5.2.17,在使用正则表达式 preg_match_all (如 preg_match_all("/ni(.*?)wo/", $html, $matches);)进行分析匹配比较长的字符串 $html 时(大于10万字节,一般用于分析采集回来的网页源码),apache服务器会崩溃自动重启。
在apache错误日志里有这样的提示:
复制代码 代码如下:
[thu apr 11 18:31:31 2013] [notice] parent: child process exited with status 128 -- restarting.
[thu apr 11 18:31:31 2013] [notice] apache/2.2.9 (win32) php/5.2.17 configured -- resuming normal operations
[thu apr 11 18:31:31 2013] [notice] server built: jun 13 2008 04:04:59
[thu apr 11 18:31:31 2013] [notice] parent: created child process 2964
[thu apr 11 18:31:31 2013] [notice] disabled use of acceptex() winsock2 api
[thu apr 11 18:31:31 2013] [notice] child 2964: child process is running
[thu apr 11 18:31:31 2013] [notice] child 2964: acquired the start mutex.
[thu apr 11 18:31:31 2013] [notice] child 2964: starting 350 worker threads.
[thu apr 11 18:31:31 2013] [notice] child 2964: listening on port 80.
[thu apr 11 18:31:31 2013] [notice] apache/2.2.9 (win32) php/5.2.17 configured -- resuming normal operations
[thu apr 11 18:31:31 2013] [notice] server built: jun 13 2008 04:04:59
[thu apr 11 18:31:31 2013] [notice] parent: created child process 2964
[thu apr 11 18:31:31 2013] [notice] disabled use of acceptex() winsock2 api
[thu apr 11 18:31:31 2013] [notice] child 2964: child process is running
[thu apr 11 18:31:31 2013] [notice] child 2964: acquired the start mutex.
[thu apr 11 18:31:31 2013] [notice] child 2964: starting 350 worker threads.
[thu apr 11 18:31:31 2013] [notice] child 2964: listening on port 80.
经过查阅apache官方以及论坛资料后,发现win平台下用正则 preg_match_all 或preg_match 分析比较长的字符串时,导致apache崩溃重启的原因是windows平台下默认分配的线程堆栈空间 threadstacksize 太小导致的。 win32默认只有256kb,而在 linux下默认值是 8m,这就是为什么同样的程序在 linux平台下正常,而在 win平台下不正常的原因。
根据pcre library的官方说明:256 kb 的堆栈空间对应的pcre.recursion_limit大小应该不超过524。
here is a table of safe values of pcre.recursion_limit for a variety of executable stack sizes:
下面就是一张stacksize和pcre.recursion_limit对应的建议安全值,超过这个数值就极有可能发生堆栈溢出,apache crash:
复制代码 代码如下:
stacksize pcre.recursion_limit
64 mb 134217
32 mb 67108
16 mb 33554
8 mb 16777
4 mb 8388
2 mb 4194
1 mb 2097
512 kb 1048
256 kb 524
64 mb 134217
32 mb 67108
16 mb 33554
8 mb 16777
4 mb 8388
2 mb 4194
1 mb 2097
512 kb 1048
256 kb 524
如果你没有调整堆栈大小,就必须在使用正则的php页面最开头加入:
复制代码 代码如下:
<?php
ini_set("pcre.recursion_limit", "524"); // php default is 100,000.
?>
ini_set("pcre.recursion_limit", "524"); // php default is 100,000.
?>
查看具体的错误可以使用下面的代码:
复制代码 代码如下:
$resultsarray = preg_match_all("/table.*?<a>/isu", $html, $contents);
if ($resultsarray === 0){
echo get_pcre_err();
}
function get_pcre_err(){
$pcre_err = preg_last_error(); // php 5.2 and above.
if ($pcre_err === preg_no_error) {
$msg = 'successful non-match.';
} else {
// preg_match error!
switch ($pcre_err) {
case preg_internal_error:
$msg = 'preg_internal_error';
break;
case preg_backtrack_limit_error:
$msg = 'preg_backtrack_limit_error';
break;
case preg_recursion_limit_error:
$msg = 'preg_recursion_limit_error';
break;
case preg_bad_utf8_error:
$msg = 'preg_bad_utf8_error';
break;
case preg_bad_utf8_offset_error:
$msg = 'preg_bad_utf8_offset_error';
break;
default:
$msg = 'unrecognized preg error';
break;
}
}
return($msg);
}
if ($resultsarray === 0){
echo get_pcre_err();
}
function get_pcre_err(){
$pcre_err = preg_last_error(); // php 5.2 and above.
if ($pcre_err === preg_no_error) {
$msg = 'successful non-match.';
} else {
// preg_match error!
switch ($pcre_err) {
case preg_internal_error:
$msg = 'preg_internal_error';
break;
case preg_backtrack_limit_error:
$msg = 'preg_backtrack_limit_error';
break;
case preg_recursion_limit_error:
$msg = 'preg_recursion_limit_error';
break;
case preg_bad_utf8_error:
$msg = 'preg_bad_utf8_error';
break;
case preg_bad_utf8_offset_error:
$msg = 'preg_bad_utf8_offset_error';
break;
default:
$msg = 'unrecognized preg error';
break;
}
}
return($msg);
}
对于正则的修饰符 isu 说明:
复制代码 代码如下:
i: 表示in-casesensitive,即大小写不敏感
s: pcre_dotall,表示点号可以匹配换行符。
u: 表示pcre_ungreedy,表示非贪婪,相当于perl/python语言的.*?,在匹配过程中,对于.*正则,一有匹配立即执行,而不是等.*搜索了所有字符再一一返回
s: pcre_dotall,表示点号可以匹配换行符。
u: 表示pcre_ungreedy,表示非贪婪,相当于perl/python语言的.*?,在匹配过程中,对于.*正则,一有匹配立即执行,而不是等.*搜索了所有字符再一一返回
在使用正则表达式时,我们应该尽量避免递归调用,递归容易导致堆栈溢出。比如:
/<table((?!<table).)*?<\/a>/isu 就会发生错误,而使用 /<table.*?<\/a>/i 就正常。
那么如何增加win平台下 threadstacksize 的大小呢? 在apache的配置文件 httpd.conf 里启用 “include conf/extra/httpd-mpm.conf”(删除前面的注释#),然后在 httpd-mpm.conf 文件里的 mpm_winnt_module 配置模块里设置 “threadstacksize 8400000”即可(大约8m)。
复制代码 代码如下:
<ifmodule mpm_winnt_module>
threadstacksize 8400000
threadsperchild 200
maxrequestsperchild 10000
win32disableacceptex
</ifmodule>
threadstacksize 8400000
threadsperchild 200
maxrequestsperchild 10000
win32disableacceptex
</ifmodule>
这里需要注意的是,32位的apache程序只能最多使用大约2gb内存空间! 因此,threadstacksize 和threadsperchild 的值相乘后(8m * 200)不应该超过2g,否则无法启动apache,出现的错误日志如下:
复制代码 代码如下:
[thu apr 11 20:02:45 2013] [crit] (os 8)存储空间不足,无法处理此命令。 : child 4832: _beginthreadex failed. unable to create all worker threads. created 212 of the 220 threads requested with the threadsperchild configuration directive.
通过上面的提示,小编可以告诉大家的是在我的这台服务器上,当线程堆栈大小设为8m时,我可以设置的线程数最多是212个。