windows下如何解决PHP调用的外部程序超时阻塞问题
我近期做一个东东,大致构架是:
访问者通过web提交c程序,服务端调用编译器编译并且在编译完成后运行编译后的程序,将运行结果传回访问者浏览器。
且不考虑安全性,因为访问者可以都认为是可以信赖的,命令行编译器最终要返回的,但是对于临时编译的程序,尽管用户是可以信赖的,但是不排除因为不完善而出现死锁,php调用后启动的进程无法返回而超时,此进程一直存在直到服务器重启,久而久之,服务器端资源就要耗尽。考虑到php本身执行的时候没有提供多线程和进程管理功能(可能是我没有看到这方面的资料),使用不管是exec,还是popen等,主程序一旦阻塞就无法自拔,所以必须预留一个线程在必要时管理启动的进程.而我又不想对服务器配置做改动。于是想到自己写一个程序管理启动的进程,php间接通过这个程序调用编译后的客户程序,实现对客户程序超时的控制。
下面是测试用的php程序。
<?
//filename: test1.php
$cmd="test.exe 24 154";// input you command here$cmd="process.exe 5000 ".$cmd;
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("file", "error-output.txt", "w+"), // stderr is a file to write to
);
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
    // $pipes now looks like this:
    // 0 => writeable handle connected to child stdin
    // 1 => readable handle connected to child stdout
    // Any error output will be appended to /tmp/error-output.txt    fwrite($pipes[0], '12345678');// input integer to scanf, you should add '\n' at the end of string as 'Enter'; fclose($pipes[0]);    
    while(!feof($pipes[1])) {
        echo nl2br(fgets($pipes[1], 1024));
    }
    fclose($pipes[1]);
    // It is important that you close any pipes before calling
    // proc_close in order to avoid a deadlock
//proc_terminate($process);
    $return_value = proc_close($process);    echo "<br/>command returned $return_value\n";
}
?>process.exe就是我编写提供给php的代理程序。
使用方法:
process.exe max_time_limit client_exe_name [ param1_to_client, param2_to_client,... ...]
max_time_limite是以毫秒为单位。一般情况下,我们使用proc_open()是这样的
$cmd="test.exe 24 154";通过process.exe间接调用变成:
process.exe 1000 test.exe 24 154
当调用的客户程序超时,process就会杀死进程并返回。以下是process的源程序,Dev-C++下编译通过。
//filename:process.cpp
#include <iostream>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
#include <string.h>
using namespace std;
DWORD WINAPI ThreadFunc( LPVOID lpParam );
char gargv[255];
bool end_thread=true;
PROCESS_INFORMATION pi;
int main(int argc, char *argv[])
{
      if(argc<2) 
      {
      printf("Too few parameters");
      return 1;
      }
    
  strcpy(gargv,argv[2]); 
  char blank[]=" ";
  for(int i=3;i<argc;i++)
  {
   strcat(gargv,blank);
   strcat(gargv,argv[i]);
   }
   //printf("%s",gargv);  
   
    DWORD dwThreadId, dwThrdParam = 1; 
    HANDLE hThread; 
    char szMsg[80];    
    hThread = CreateThread( 
        NULL,                        // default security attributes 
        0,                           // use default stack size  
        ThreadFunc,                  // thread function 
        &dwThrdParam,                // argument to thread function 
        0,                           // use default creation flags 
        &dwThreadId);                // returns the thread identifier 
 
   // Check the return value for success. 
 
   if (hThread == NULL) 
   {
      wsprintf( szMsg, "CreateThread failed." ); 
      MessageBox( NULL, szMsg, "main", MB_OK );
   }
   else 
   {
     Sleep(500);
     if(end_thread)
     {   
          Sleep(atoi(argv[1]));
          if(pi.hProcess)
               {
                   TerminateThread(pi.hThread ,0);     
                   TerminateProcess(pi.hProcess , 0);
               }
     }
     CloseHandle( hThread );
   }
  return 0;
}
DWORD WINAPI ThreadFunc( LPVOID lpParam )
{
    STARTUPINFO si;
    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );    // Start the child process. 
    if( !CreateProcess( NULL, // No module name (use command line). 
        gargv, // Command line. 
        NULL,             // Process handle not inheritable. 
        NULL,             // Thread handle not inheritable. 
        TRUE,            // Set handle inheritance to FALSE. 关键是这个参数,为TRUE则进程使用父控制台窗口。
        0,                // No creation flags. 
        NULL,             // Use parent's environment block. 
        NULL,             // Use parent's starting directory. 
        &si,              // Pointer to STARTUPINFO structure.
        &pi )             // Pointer to PROCESS_INFORMATION structure.
    ) 
    {
        return 1;
    }    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );
    // Close process and thread handles. 
    end_thread=false;
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
}测试用的客户程序test.cpp:
#include<stdio.h>
int main(int argc,char *argv[])
{
    for(int i=0;i<argc; i++)
    {
        printf("This is from command line: %s \n",argv[i]);
    }
    int j;
    printf("Input a integer: ");
    scanf("%d",&j);
    printf("\n This is from proc_open input: %d",j);
    return 0;
}

解决方案 »

  1.   

    无限级目录与目录之间的复制代码
    windows:
    exec("xcopy32/s src_dir di_dir",$str,$var);
    linux:
    exec("cp -r ........
    如果需要复杂交互操作可以使用管道
    popen();
    或者更加强大的
    proc_open();//php4.3.0
      

  2.   

    4.调试时对于逻辑错误,可以采用2中方法,将错误处理对象隐藏的加入调试对象内部,可以将错误信息分布到各个对象内部,这样可以明显表示错误的宿主。因为全局对象$OBJ记录了所以实例的名字,又可以全局访问各个对象的错误信息。
    <?
    //错误处理供调试,可以将错误分布到各个对象内部,但是又提供全局访问能力
    $err_code[1]="error 1";
    $err_code[2]="error 2";
    $err_code[3]="error 3";
    $err_code[4]="error 4";
    $err_code[5]="error 5";class Info
    {
        var $type;
        var $name;
    var $error; function AddError($code)
    {
    $this->error[]=$code;
    } function ShowMyError()
    {   
    global $err_code;
    for($i=0;$this->error[$i];$i++)
    echo $err_code[$this->error[$i]]."<br>";
    }
    }
    class Obj
    {
        var $objs;
    function ShowAllError()
    {
        for($i=0;$this->objs[$i];$i++)
        {
               $tmp=$this->objs[$i];
               global $$tmp;
           echo  $$tmp->info->ShowMyError();
        }
    }
    }
    $OBJ=new Obj;
    function NewObj($type)
    {
        $out=new $type;
        $info=new Info;
        $info->type=$type;
        $out->info=$info;
        return $out;
    }
    function MyNew($type,$name)
    {
        global $$name,$OBJ;
        $$name=NewObj($type);
        $$name->info->name=$name;
        $OBJ->objs[]=$name;
    }class derive1
    {
        function do1()
    {
    $i=1;
    if($i==1) 
    $this->info->AddError(1);
    else
    $this->info->AddError(2);
    }
    }class derive2
    {
    function do2()
    { $i=2;
    if($i==1) 
    $this->info->AddError(1);
    else
    $this->info->AddError(2); }
    }
    ///////////////////////////////
    MyNew("Derive1","Inst1");
    MyNew("Derive1","Inst2");
    MyNew("Derive2","Inst3");
    $Inst1->do1();
    $Inst3->do2();
    echo "The following lines list error in Inst1:<br>";
    $Inst1->info->ShowMyError();
    echo "The following lists all error:<br>";
    $OBJ->ShowAllError();
    ?>
      

  3.   

    5。类似的,可以设立全局消息处理模块,负责将开始运行时接受到的初始参数进行判断,映射,然后分发到各个具体处理模块的info对象中,这样每个模块只要负责处理本身info对象中的消息队列就行了,不用分散的进行重复判断和处理了。而消息处理过程很多部分操作是通用的,可以先在基类中进行定义,然后定向到各个回调函数就行了。当完成了框架,剩下的工作就是做消息映射和编写响应的处理用回调函数。对象间通讯也可以采用发送消息的办法来处理。
    很多中应用可以使用这种模式。
      

  4.   

    在3中如果加入字符串剖析传入的函数名,则可以实现对对象成员函数的调用,可以传入如"$Instname->function"之类的函数.
      

  5.   

    类中静态变量模拟
    class class_test{
        var $sType;
        var $sCnt;
    function class_test(){
    $this->sCnt = &$GLOBALS[get_class($this)]['sCnt'];
    $this->sCnt++;
    }
    }
    $t1 = new class_test();
    $t2 = new class_test();
    $t3 = new class_test();
    echo $t1->sCnt."<BR>";
    echo $t2->sCnt."<BR>";
    echo $t3->sCnt."<BR>";
    输出都是3
      

  6.   

    类中的属性变量的另类访问方法:
    class class_test{
        var $sType;
        var $sCnt;
    function class_test(){
    $this->sCnt = &$GLOBALS[get_class($this)]['sCnt'];
    $this->sCnt++;
    }
    }
    $t1 = new class_test();
    访问属性sCnt可以用$t1->sCnt
    或$t1['sCnt']
    在某些特殊情况下用得上,
    如果你要清空类某个类属性,就要用到unset($t1['scnt']);
      

  7.   

    谢谢xucarry(小草) ,本质其实还是隐藏的创建了一个全局变量吧.
      

  8.   

    修正2如下,采用xucarry(小草) 的方法,模拟静态变量,可以达到更好的隐藏和封装效果.
    <?php
    //////////////////////////////////////////////////////////
    ////////////简便加入隐藏信息作者:mao_junhua//////////////////
    //////////////////////////////////////////////////////////class Info
    {
        var $type;
        var $name;
    var $objs;
    function Info()
    {
    $this->objs=&$GLOBALS[get_class($this)]['objs'];
    }
        function ShowInfo()
        {
            echo 'My type is: '.$this->type.', My name is: '.$this->name.'<br>';
        }
    function ShowAllObjInfo()
    {
        for($i=0;$this->objs[$i];$i++)
        {
               $tmp=$this->objs[$i];
               global $$tmp;
           echo  $$tmp->info->ShowInfo();
        }
    }
    }function NewObj($type)
    {
        $out=new $type;
        $info=new Info;
        $info->type=$type;
        $out->info=$info;
        return $out;
    }
    function MyNew($type,$name)
    {
        global $$name,$OBJ;
        $$name=NewObj($type);
        $$name->info->name=$name;
        $$name->info->objs[]=$name;
    }/////////////////////////////////////////////////////////////class derive1
    {}class derive2
    {
    }
    ///////////////////////////////MyNew("Derive1","Inst1");
    MyNew("Derive1","Inst2");
    MyNew("Derive2","Inst3");
    $Inst1->info->ShowInfo();
    $Inst1->info->ShowAllObjInfo();?>
    运行结果:
    My type is: Derive1, My name is: Inst1
    My type is: Derive1, My name is: Inst1
    My type is: Derive1, My name is: Inst2
    My type is: Derive2, My name is: Inst3
      

  9.   

    up一下,PHP5前继承太多问题。
      

  10.   

    很简单,但是我天天用的类:
    class debug{
         /**
         * @return void
         * @param mixed $var
         * @param str $title
         * @desc trace
         */
         function trace($var, $title = null){
             if(!is_null($title)) echo '<b>' . $title . ":</b>\n";
             echo "<pre>" . print_r($var, true) . "</pre>\n";
             }
         /**
         * @return void
         * @param mixed $var
         * @desc Pop values use JavaScript 
         */
         function alert($var){
             if(is_bool($var)) $var = $var ? "true" : "false";
             echo "<script>alert(\"" . str_replace("\n", '\n', print_r($var, true)) . "\");</script>\n";
             }
        }
      

  11.   

    谢谢episome(3ants.org)跟贴,我的做法是:如果采用oo来做,首先考虑的就是一个共有基类,具有所有对象的抽象特征,包括错误处理,统一获取外界传递参数,模块间通讯接口等,并且将一部分通用操作如显示自定义样式等通用操作总结成为全局函数,错误处理最好采用错误码,通过查询错误对照表来显示错误。尽量将外观和功能分离。划分模块和模块间接口要设计得当,这样顶层模块的抽象操作可以根据需要具体实现到不同的底层模块上面.如顶层需要PrintError(),但是可以具体是PrintError到屏幕,也可以输出到文件.
      

  12.   

    to episome(3ants.org) :
    我的意思不是将文件操作作为基类,而是将某些I/O抽象出来,在基类里面抽象操作,然后再到派生类里面具体实现到文件操作或者是数据库操作等其他方向.因为php无法做到用基类类型指针指向派生类来实现多态,只能绕着弯子来实现,如我1中提到的class base的ShowSelf函数,如果Show操作是所有派生对象共同的需要的操作,则我们可以封装到基类,但是Show中有一个操作ShowSelf是未知的,要根据不同的派生对象特定才能确定,我们就在基类中不定义这个ShowSelf,改在派生类中定义。
      

  13.   

    PHP不懂:(
    不知道vbs在oo方面能做到什么程度.
    因为现在做asp.net开发,所以没有对vbs的oo特性做过多研究,
    希望"超级大笨狼"等vbs高手能来讲解一下.
      

  14.   

    上面贴子里面的静态变量模拟的方法不是很好,给你们个真正的静态变量的存储类把!
    <?
    class StatVar{
      function &setInit(){
        static $static = '';
        return $static;
      }
      function set($key,$value){
        $var = &StatVar::setInit();
        $var[$key] = $value;
      }
      function &get($key){
        $var = &StatVar::setInit();
        return $var[$key];
      }
    }
    //例子如下:
    StatVar::set('aa', 'bb');
    echo StatVar::get('aa');
    ?>