欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

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.

    经过查阅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

如果你没有调整堆栈大小,就必须在使用正则的php页面最开头加入:

复制代码 代码如下:
<?php
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);
}

对于正则的修饰符 isu 说明:

复制代码 代码如下:
i: 表示in-casesensitive,即大小写不敏感
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>

    这里需要注意的是,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个。